解法一: 一步步挪
步骤
1. 当k是0或数组长度整数倍时,旋转之后的数组就是原数组,所以只需要考虑k%nums.size()不为0的情况,将余数取出来赋给k,这个k才是真正需要旋转的数值。
2. 第一步中取得的余数不为0时才需要做旋转,否则不用。
3. 根据k的值,一步步挪:
1)k只要不为0,每次挪动1位后k自减1,直到k减到0为止。
2)挪动1位的做法是:将每一个数都往后挪一位,将最后的数挪到首位。
代码
class Solution {
public:
void rotate(vector<int>& nums, int k) {
if (k >= nums.size()) k %= nums.size();
if (k != 0) {
while (k) {
int temp = nums[nums.size() - 1];
for (int i = nums.size()-2; i>= 0; i--) {
nums[i + 1] = nums[i];
}
nums[0] = temp;
k--;
}
}
}
};
分析
时间复杂度:O(kn),当k很大时会超时。
解法二:分段反转再将全数组反转
思路
这个旋转数组的问题,可以按照题意一步步挪,也可以很巧妙的将数组分成两段分别反转,再将整个数组反转就可以得到结果。
举例:
[1,2,3,4,5,6,7] 和 k = 3
分别反转[1,2,3,4]和[5,6,7]变成[4,3,2,1]和[7,6,5],拼起来就是[4,3,2,1,7,6,5],最后将整个数组进行反转,得到[5,6,7,1,2,3,4],这正是要求的结果。
步骤
1. 当k为数组长度的整数倍时,旋转后的数组就是原数组,所以仅考虑k不是数组长度整数倍的情况。
1) 当k小于nums.size()时,k不变
2) 当k大于nums.size()时,k取k除以nums.size()的余数。
2. 由于k是取余数得到的,k可能为0,根据上面的分析,k只考虑不为0的情况,仅当k不为0时:
1) 反转 第0(即第一个) 到 第nums.size()-k-1 的元素。
2) 反转 第nums.size()-k 到 第 nums.size()-1(即最后一个) 的元素。
3) 反转 第0 到 第nums.size()-1 的元素,即全部反转。
3. 实现反转函数:
1) 函数设计:void reverse1(vector<int>& a, int s, int e)
2) 参数说明: vector<int>& a表示传入的数组a,int s表示要反转的start位置的元素在数组中的下标,int e表示要反转的end位置的元素在数组中的下标
3) 实现步骤:
s、e是双指针,初始情况分别指向首位元素,用while循环,每次让s、e指向的元素互换位置,然后让s、e同时向中间逼近,终止条件是两指针相遇。
代码
class Solution {
public:
void rotate(vector<int>& nums, int k) {
if(k>=nums.size())
k%=nums.size();
if(k!=0)
{
reverse1(nums,0,nums.size()-k-1);
reverse1(nums,nums.size()-k,nums.size()-1);
reverse1(nums,0,nums.size()-1);
}
}
void reverse1(vector<int>& a,int s,int e)
{
while(s<e)
{
int temp=a[e];
a[e]=a[s];
a[s]=temp;
e--;
s++;
}
}
};
分析
时间复杂度:O(n)。
空间复杂度:O(1)。
解法三:环状替换
思路
对于旋转数组的问题,只要k不是nums.size()的整数倍,旋转之后的数组和原数组就会不同,并且每个元素都不在原先的位置上,即需要将数组中所有的元素都进行换位,所以旋转数组实际上一共需要换元素的次数是nums.size()次。
这个解法不是很好理解,找到一个非常形象的图解
(作者:xfzhao)
(链接:https://leetcode-cn.com/problems/rotate-array/solution/xuan-zhuan-shu-zu-yuan-di-huan-wei-xiang-xi-tu-jie/)
(来源:力扣(LeetCode))
两种情况:
一、A,B,C,D,E五位同学换座位,要求每个人都往后挪三个:
二、A,B,C,D四位同学换座位,要求每个人都往后挪两个:
对于长度为n的数组,整体移动k个位置,会出现上述两种情况:
1) n、k的最大公约数为1,1次遍历可以完成所有交换
2) n、k的最大公约数不为1,需要最大公约数次遍历
比如A、B、C、D、E、F,n为6,此时k假设为2,那么n、k的最大公约数为2,则需要两次遍历完成所有交换。
第一次遍历换的元素是:
第二次遍历交换的元素是:
每一次遍历都只会在自己的小循环中不停的换。共有n、k最大公约数次遍历,每次遍历的起始位置依次取前最大公约数个元素。
步骤
1. k大于等于nums.size()时,k = k % nums.size()
2. k不为0时,执行以下步骤
3. 初始化计数参数count,count表示交换元素的次数,一共要执行nums.size()次
4. 两层循环
1) 外循环终止条件是count = nums.size(),start每次自加一,因为每次遍历的起始位置每次都要向后一位
2) 当前位置curr从start位置开始,用pre存放curr位置的元素。
3) 开一个新的循环来做这一组元素的交换,循环终止条件是当前位置curr和本组交换的元素初始位置start重合,循环具体要做的事:
找到curr要去的位置next --> next位置的元素存放在temp中 --> 当前元素pre放到要去的next位置 --> temp放到pre --> 当前位置更新成next的位置(也就是为next位置的元素找下家) --> count自加一,表示经过这些步骤已经换了一次了,最后是要换nums.size()次。
代码
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int len = nums.size();
if (k >= len) k %= len;
if (k != 0) {
int count = 0;
for (int start = 0; count < len; start++) {
int curr = start;
int pre = nums[curr];
do {
int next = (curr + k) % len;
int temp = nums[next];
nums[next] = pre;
pre = temp;
curr = next;
count++;
} while (curr != start);
}
}
}
};
分析
时间复杂度: O(n),一共只换了n次。
空间复杂度:O(1),使用了常数个额外空间。