旋转数组算法

问题描述:

给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。

进阶:

  1. 尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。
  2. 你可以使用空间复杂度为 O(1) 的 原地 算法解决这个问题吗?

例子:

输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]

输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]

算法分析与实现:

题目分析:
将数组 n u m s [ n ] nums[n] nums[n]中的元素向右移动 k 个位置,其意思相当于,将数组首尾相连,形成一个环,然后按顺时针移动 k k k个位置。
很明显,题目将一个数组分成两部分,然后交换两部分的位置;

1. 方法一:
增加一个数组 a [ n ] a[n] a[n],这个数组先将后面 k k k个元素按序存入 a [ n ] a[n] a[n]中,然后再将前面 n − k n-k nk个元素存入 a [ n ] a[n] a[n]即可。最后赋值 n u m s = a nums=a nums=a。这样需要用两次 f o r for for循环,为了代码简洁,可以利用循环来替代。即 a [ ( i + k ) a[(i+k) a[(i+k)% n ] = n u m s [ i ] n]=nums[i] n]=nums[i],意思是将数组全部往后移 k k k个位置,超出最大位置,就从0号位置排列。
例如:
输入: n u m s = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 ] , k = 3 nums = [1,2,3,4,5,6,7], k = 3 nums=[1,2,3,4,5,6,7],k=3
第一次存入: a [ n ] = [ 5 , 6 , 7 ] a[n]=[5,6,7] a[n]=[5,6,7] //先将后面 k k k个元素按序存入 a [ n ] a[n] a[n]
第一次存入: a [ n ] = [ 5 , 6 , 7 , 1 , 2 , 3 ] a[n]=[5,6,7,1,2,3] a[n]=[5,6,7,1,2,3]
代码如下:

//方法一:
class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        int n = nums.size();
        vector<int>abs(n);
        k = k % n; //限制k只能在0~n-1之间,防止数组内存不足
        for(int i=0; i < n; i++) {
            abs[(i+k)%n] = nums[i];//排序
        }
        nums.assign(abs.begin(),abs.end());//换回给原数组相当于赋值语句。
    }
};
//如果代码看不懂,可能是因为没怎么用过c++容器vector,以及相关函数,这个我也没系统学过,
//遇到就查询用法即可。个人觉得,函数全都是工具,工具会用就行,不会就查说明书。

复杂度分析:
时间复杂度: O ( n ) O(n) O(n),其中 n n n 为数组的长度。
空间复杂度: O ( n ) O(n) O(n)


2. 方法二:
方法一增加了空间复杂度,需要额外数组消耗,如果原数组上变化,也是可以的,方法如下:
构造一个倒叙函数 r e v e r c e ( ) reverce() reverce(),这个函数功能是将数组倒叙排列。以 n u m s = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 ] , k = 3 nums = [1,2,3,4,5,6,7], k = 3 nums=[1,2,3,4,5,6,7],k=3为例子:

1.第一步:将整个数组倒叙排列;得 n u m s = [ 7 , 6 , 5 , 4 , 3 , 2 , 1 ] nums = [7,6,5,4,3,2,1] nums=[7,6,5,4,3,2,1]
2.第二步:将前面 k k k个数倒叙排列;得 n u m s = [ 5 , 6 , 7 , 4 , 3 , 2 , 1 ] nums = [5,6,7,4,3,2,1] nums=[5,6,7,4,3,2,1]
3.第三步:将后面 n − k n-k nk个数倒叙排列;得 n u m s = [ 5 , 6 , 7 , 1 , 2 , 3 , 4 ] nums = [5,6,7,1,2,3,4] nums=[5,6,7,1,2,3,4]
代码如下:

//方法二:
class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        int n = nums.size();
        k = k % n;//限制k只能在0~n-1之间,防止数组内存不足
        reverse(nums,0,n-1);//原数组倒叙排列
        reverse(nums,0,k-1);//前k个数组倒叙排列
        reverse(nums,k,n-1);//后n-k个数排序排列
    }
    void reverse(vector<int>& nums,int start,int end) {
        while(start < end) {
            swap(nums[start],nums[end]);
            start++;
            end--;
        }
    }
};

复杂度分析:
时间复杂度: O ( n ) O(n) O(n),其中 n n n 为数组的长度。
空间复杂度: O ( 1 ) O(1) O(1)


**3. 方法三:** 这个方法有点复杂,如果想深入,可以去看这位[旋转数组](https://leetcode-cn.com/problems/rotate-array/solution/xuan-zhuan-shu-zu-yuan-di-huan-wei-xiang-xi-tu-jie/)大佬的解释,其大概思路如下:

分两种情况讨论:
1、当 k k k n n n无有公因数的时候,可以一次循环就可以完成交换。
其做法是将数组第0号位置与 ( j + k ) (j+k) (j+k)% n n n(j=0)进行交换,因为 k k k n n n无有公因数,所以,进行n-1次交换后,又回到原来的位置,即 ( j + k ) (j+k) (j+k)% n = 0 n=0 n=0,此时,数组完成排序,例如 n u m s = [ 1 , 2 , 3 , 4 ] , k = 3 nums = [1,2,3,4], k = 3 nums=[1,2,3,4],k=3,其过程如下:
第一次,数组第0号位置和第 ( 0 + 3 ) (0+3) (0+3)% 4 = 3 4=3 4=3号位置交换顺序,得 n u m s = [ 4 , 2 , 3 , 1 ] nums = [4,2,3,1] nums=[4,2,3,1]
第二次,数组第0号位置和第 ( 3 + 3 ) (3+3) (3+3)% 4 = 2 4=2 4=2号位置交换顺序,得 n u m s = [ 3 , 2 , 4 , 1 ] nums = [3,2,4,1] nums=[3,2,4,1]
第三次,数组第0号位置和第 ( 2 + 3 ) (2+3) (2+3)% 4 = 1 4=1 4=1号位置交换顺序,得 n u m s = [ 2 , 3 , 4 , 1 ] nums = [2,3,4,1] nums=[2,3,4,1];完成排序。
2、当 k k k n n n有公因数的时候,需要进行最大公因数次循环就才可以完成交换。
每一次循环做法和方法一一样。例如 n u m s = [ 1 , 2 , 3 , 4 ] , k = 2 nums = [1,2,3,4], k = 2 nums=[1,2,3,4],k=2,其过程如下:
第一次,数组第0号位置和第 ( 0 + 2 ) (0+2) (0+2)% 4 = 2 4=2 4=2号位置交换顺序,得 n u m s = [ 3 , 2 , 1 , 4 ] nums = [3,2,1,4] nums=[3,2,1,4]
第二次,数组第0号位置和第 ( 2 + 2 ) (2+2) (2+2)% 4 = 0 4=0 4=0号位置交换顺序,由于陷入循环,所以应该退出这次循环,进行下一次循环,此时将1号位置作为交换点,即数组第1号位置和第 ( 1 + 2 ) (1+2) (1+2)% 4 = 3 4=3 4=3号位置交换顺序,得 n u m s = [ 3 , 4 , 1 , 2 ] nums = [3,4,1,2] nums=[3,4,1,2]
循环退出条件是数组 i i i号位置是否等于 ( i + k ) (i+k) (i+k)% n n n
代码如下:

//第三种:
class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        int n = nums.size();
        k = k % n;
        int count = gcd(n,k);
        cout<<count;
        int j;
        for (int i=0; i<count; i++) {
            j=i;//将数组i号位置作为交换点
            do{
                j=(j+k)%n;
                swap(nums[i],nums[j]);    //nums[i],nums[j]交换位置
            }while(i!=j); //不满足退出
        }
    }
};

复杂度分析:
时间复杂度: O ( n ) O(n) O(n),其中 n n n 为数组的长度。
空间复杂度: O ( 1 ) O(1) O(1)


总结:

做算法题的时候,应该将思路缕清----用公式推到出具体步骤,或者用流程图将每一步写清楚,同时将每一步需要条件写明。只有这样,写出来的程序才会逻辑情绪无明显bug。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值