设计一个算法,把一个含有N个元素的数组循环右移K位,要求时间复杂度为 O(N), 且只允许使用两个附加变量。
解法一:
简单的办法是,每次将数组中的元素右移一位,循环K次。abcd1234 -> 4abcd123 -> 34abcd12。
代码如下:
public static void rightShift(int[] array, int len, int k){
while(k > 0){
int temp = array[len-1];
for(int i = len-1; i > 0; i--){
array[i] = array[i-1];
}
array[0] = temp;
k--;
}
}
算法的时间复杂度为 O(K*N)。
当K是一个远大于N的数时,上面的解法就需要改进了。当 k=50000000时,移位所需要的时间如下:
解法二:
观察循环右移的特点后,发现每个元素右移N位后,都会回到原来的位置,因此右移 K 位跟右移 k%N 位的结果是一样的,算法改进如下:
public static void rightShift(int[] array, int len, int k){
k %= len;
while(k > 0){
int temp = array[len-1];
for(int i = len-1; i > 0; i--){
array[i] = array[i-1];
}
array[0] = temp;
k--;
}
}
解法三:
假设原数组为12345678,循环右移4位后变为56781234。其中 5678 和 1234的顺序是不变的,可以将这两段当做整体,右移K位就是将这两段的顺序交换。
- 逆序排列 1234: 12345678 -> 43215678;
- 逆序排列 5678: 43215678 -> 43218765;
- 整体逆序: 43218765 -> 56781234。
代码如下:
public static void reverse(int[] array, int start, int end){
for(; start < end; start++, end--){
int temp = array[end];
array[end] = array[start];
array[start] = temp;
}
}
public static void rightShift(int[] array, int len, int k){
k %= len;
reverse(array, 0, len-k-1);
reverse(array, len-k, len-1);
reverse(array, 0, len-1);
}
当k=50000000时,所花时间如下:
可以看到效率有了明显提升。