输入 a1,a2,...,an,b1,b2,...,bn,将其变为a1,b1,a2,b2,a3,b3,...,an,bn
题目:
输入 a1,a2,...,an,b1,b2,...,bn,在 O(n)的时间,O(1)的空间将这个序列顺序改为 a1,b1,a2,b2,a3,b3,...,an,bn,
且不需要移动,通过交换完成,只需一个交换空间。(Google笔试题)
解题思路:线性方法,时间复杂度为O(n),空间复杂度O(n)
我们这里介绍一个线性的方法。线性方法的思路该如何思考呢?怎么能够在常数时间内,就确定元素的最后位置呢?而不是经过多次的交换。输入和输出的数组,都是非常有规律的,所有有希望能够找到方法,常数时间内确定最终的位置。当n=4的时候,bi原来的位置和最终的位置之间的关系如下:
我们假设最终位置为final_index,初始位置为old_index。在此例中,通过上表得:
final_index=(2 * old_index) % 7
更通用的为:final_index=(2*old_index)%(2n-1).
则对于b1,final_index(b1)=(2*4)%7=1。则将b1,换到1的位置,但此时,原来的a2,如何处理呢?和b1一样的,通过公式,找到a2的位置,依次类推。下面的表格,空白表示元素未发生变化。
在处理过程中,需要对每一个找到最终位置的元素进行标记,已经找到最终位置,空间O(n)。每一个元素,最多遍历两次,空间复杂度为O(n)。在处理完b1,a2已经处理过了,需要继续处理,这里可以按照数组遍历的顺序进行,即当前索引遍历到1,从1开始,2已经处理过了,通过标记判断。则继续处理位置3的,即使a4。这个思路,使用了O(n)的空间,可以认为,不是in-place的。
实现代码:
#include <iostream> #include <cstring> using namespace std; int GetNewIndex(int oldIndex, int n) { return 2*oldIndex % (2*n-1); } void ArrayShift(int *arr, int n) { if(arr == NULL || n <= 0) return; int flag[2*n];//标志,用来存放该数组中对应位置元素是否已经定位 memset(flag, 0, sizeof(int)*2*n); for(int i = 1; i < 2*n-1; i++) { if(flag[i] == 0) { int oldIndex = i; int tmp2 = arr[oldIndex]; while(true) { flag[oldIndex] = 1; int newIndex = GetNewIndex(oldIndex, n); int tmp1 = arr[newIndex]; arr[newIndex] = tmp2; tmp2 = tmp1; oldIndex = newIndex; if(oldIndex == i) break; } } } } int main(void) { int arr[] = {1,2,3,4,5,6,7,8,9,10}; int len = sizeof(arr) / sizeof(arr[0]); ArrayShift(arr, len/2); for(int i = 0; i < len; i++) cout<<arr[i]<<" "; cout<<endl; return 0; }
结果:
-----------------------我和我追猪的梦-----------------------------------------------------------------
作者:mickole




浙公网安备 33010602011771号