向量旋转算法

最近看《编程珠玑》,里面提到了一个常见的向量旋转问题,是指将一个数按照某点前后置换,比如【1,2,3,4,5,6,7,8,9,10】按照4旋转后,就变成了【5,6,7,8,9,10,1,2,3,4】。

之前在leetcode上也看到了这个问题,当时觉得很简单,就是把前i个数存起来,然后将后面的数据向前移动,然后在把存起来的前i个数加在后面。

《编程珠玑》上提到了两种算法,非常高效,时间复杂度为O(n),空间复杂度为O(1)。

代码1:

int gcd(int a, int b) {
    int c;
    while ((c = a%b) != 0) {
        a = b;
        b = c;
    }
    return b;
}
void rotate1(int* nums, int size,int rotateLen) {
    int times = gcd(rotateLen, size);
    for (int i = 0; i < times; i++) {
        int val = nums[i];
        int cur = i;
        int next = i + rotateLen;
        while (next % size != i) {
            nums[cur] = nums[next % size];
            cur = next % size;
            next += rotateLen;
        }
        nums[cur] = val;
    }
}

描述的算法的思路是:将num[0]保存起来,然后num[0] = num[i % size],num[i % size] = num[2*i % size],,,直到进行到这样一点待保存的数正好为num[0],然后将之前保存的临时值赋给它。如果此时,数组中所有的值都已经被遍历了一遍,那么退出循环,否则从num[1]重新开始循环。

这里有三个问题:
1、为什么这个算法是有效的?我不知道;
2、为什么一定会出现num[k * i % size] = 0的情况。因为当k足够大,比如k == size时,一定会等于0。实际上k的最大值是有讲究的。当满足条件时,一定是k*i = i和size的最小公倍数。因为最小公倍数 乘 最大公约数为 = i*size;所以等式两边变成了 k*i*gcd(i,size) = i*size;所以k*gcd = size;
这也就回答了下面问题:
3、要循环几次才会退出循环过程?答案是gcd次。


算法2:

void reverse(int *nums,int start, int end) {
    while (start < end) {
        int val = nums[start];
        nums[start] = nums[end];
        nums[end] = val;
        start++; end--;
    }
}
void rotate2(int* nums, int size,int rotateLen) {
    if (rotateLen >= size || rotateLen < 0)
        return;
    reverse(nums,0, rotateLen - 1);
    reverse(nums,rotateLen, size - 1);
    reverse(nums,0, size - 1);
}

这个算法非常美妙,它将前i个值进行翻转,将后面的n-i个值翻转,然后总体再翻转一次,完成向量旋转了。

该算法时间复杂度为O(2*n),不占用额外空间。

你可能感兴趣的:(算法)