输入 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原来的位置和最终的位置之间的关系如下:

image

我们假设最终位置为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的位置,依次类推。下面的表格,空白表示元素未发生变化。

image

在处理过程中,需要对每一个找到最终位置的元素进行标记,已经找到最终位置,空间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;
}

结果:

image

posted @ 2014-03-22 19:27  mickole  阅读(597)  评论(0)    收藏  举报