旋转数组的三种解法

前提概要与题目分析

这道题的基础思路类似于字符串左旋,即先把数组最后一个元素存起来,所有前numsSize-1个元素向后移一次(从后往前)

代码如下:

void rotate(int* nums, int numsSize, int k) {
    int tmp=0;
    k%=numsSize;
    for(int i=0;i<k;i++)
    {
        tmp=nums[numsSize-1];
        for(int j=numsSize-2;j>=0;j--)
        {
            nums[j+1]=nums[j];
        }
        nums[0]=tmp;
    }
}

但是这个方法有个严重的问题是当数组元素和循环次数多了后会超出规定的时间限制

因此需要改变一下思路:

方法一:使用额外的数组

我们可以使用额外的数组来将每个元素放至正确的位置。用 n 表示数组的长度,我们遍历原数组,将原数组下标为 i 的元素放至新数组下标为 (i+k) 的位置,最后将新数组拷贝至原数组即可。

void rotate(int* nums, int numsSize, int k) {
    int newnums[numsSize];
    for(int i=0;i<numsSize;i++)
    {
        newnums[(k+i)%numsSize]=nums[i];
    }
    for(int i=0;i<numsSize;i++)
    {
        nums[i]=newnums[i];//拷贝数组
    }
}

这里的时间复杂度和空间复杂度都是O(n)(n是数组长度)

方法二:环状替换

以上的公式推导为下面的代码作为铺垫

方法分析:

在这个方法中采用了在原数组的基础上进行位置更新的方法以避免额外开辟新的数组浪费空间,操作方法是从数组首元素开始,将首元素current拷贝到tmp临时变量中,然后跳跃k个元素,将第k个元素和tmp交换,后第k个元素作为current继续拷贝跳跃交换,不断循环直到current回到首元素,以上算遍历一次。

关于遍历的次数,用到了数学公式推导,由公式:

a(回到current所用的圈数)×n(数组元素个数)=b(一次遍历的元素个数)×k(旋转个数/跳过元素个数)

可知:an是n的倍数,an=bk也是k的倍数,因此an是n和k的公倍数(为了将未知量用已知量n,k表示)

由于遍历至current回到原点就结束,因此a要为最小圈数,an就是n,k的最小公倍数,即b=lcm(n,k)/k,所以遍历的次数就是n/b即nk/lcm(n,k)(两数相乘除以两数的最小公倍数=两数的最大公约数)因此求遍历的次数就是求n和k的最大公约数。

另外每个元素只会被遍历一次。

代码如下:

int gcd(int a, int b) {
    return b ? gcd(b, a % b) : a;
}

void swap(int* a, int* b) {
    int t = *a;
    *a = *b, *b = t;
}

void rotate(int* nums, int numsSize, int k) {
    k = k % numsSize;
    int count = gcd(k, numsSize);//求最大公约数
    for (int start = 0; start < count; ++start) {
        int current = start;
        int prev = nums[start];
        do {
            int next = (current + k) % numsSize;
            swap(&nums[next], &prev);
            current = next;
        } while (start != current);
    }
}

如果无法理解公式推导可使用单独的变量 count 跟踪当前已经访问的元素数量,当 count=n 时,结束遍历过程。

int gcd(int a,int b)
{
    return b?gcd(b,a%b):a;
}

void swap(int* a, int* b) {
    int t = *a;
    *a = *b, *b = t;
}

void rotate(int* nums, int numsSize, int k) 
{
    k%=numsSize;
    int count=0;
    int start=0;
    while(count<numsSize)
    {
        int current=start;
        int prev=nums[start];
        do{
            int next=(current+k)%numsSize;
            swap(&prev,&nums[next]);
            current=next;
            count++;
        }while(start!=current);
        start++;
    }
}
  • 时间复杂度:O(n)

  • 空间复杂度:O(1)

方法三:数组翻转

该方法基于如下的事实:当我们将数组的元素向右移动 k 次后,尾部 k%n 个元素会移动至数组头部,其余元素向后移动 k%n 个位置。

该方法为数组的翻转:我们可以先将所有元素翻转,这样尾部的 k%n 个元素就被移至数组头部,然后我们再翻转 [0,k%(n−1)] 区间的元素和 [k%n,n−1] 区间的元素即能得到最后的答案。

void swap(int* a, int* b) {
    int t = *a;
    *a = *b, *b = t;
}

void reverse(int* nums, int start, int end) {
    while (start < end) {
        swap(&nums[start], &nums[end]);
        start += 1;
        end -= 1;
    }
}

void rotate(int* nums, int numsSize, int k) {
    k %= numsSize;
    reverse(nums, 0, numsSize - 1);
    reverse(nums, 0, k - 1);
    reverse(nums, k, numsSize - 1);
}

  • 时间复杂度:O(n)

  • 空间复杂度:O(1)

另外如果是字符串的话还可以用stract函数做拼接实现旋转

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值