题目:跳转至 189. 旋转数组
给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。
进阶:
- 尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。
- 你可以使用空间复杂度为 O(1) 的 原地 算法解决这个问题吗?
示例 1:
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右旋转 1 步: [7,1,2,3,4,5,6]
向右旋转 2 步: [6,7,1,2,3,4,5]
向右旋转 3 步: [5,6,7,1,2,3,4]
示例 2:
输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
解释:
向右旋转 1 步: [99,-1,-100,3]
向右旋转 2 步: [3,99,-1,-100]
提示:
- 1 <= nums.length <= 2 * 104
- -231 <= nums[i] <= 231 - 1
- 0 <= k <= 105
class Solution {
public:
void rotate(vector<int>& nums, int k) {
}
};
思路:
最简单粗暴的,按题目意思每循环一次就把数组首位调换,其他位置右移位一次,时间复杂度肯定不够。
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int len=nums.size();
if(len<=1)
return;
while(--k>=0){
int tmp=nums[len-1];
for(int i=len-2;i>=0;--i)
nums[i+1]=nums[i];
nums[0]=tmp;
}
}
};
仔细看原数组和结果数组可以发现:
nums:[1, 2, 3, 4, 5, 6, 7]
右旋转3位后得到:[5, 6, 7, 1, 2, 3, 4]
借助的新数组:[5, 6, 7]
即是把后三位移到数组开头,那只要借助一个新的数组,从末尾遍历读取 k 个值,原数组整体右移 k 位,再把新数组中的值填入原数组即可。注意 k 与数组长度的大小关系。
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int len=nums.size();
if(len<=1)
return;
if(k>=len)
k%=len;
vector<int> helper(k,0);
for(int i=len-k;i<len;++i)
helper[i-len+k]=nums[i];
for(int i=len-1-k;i>=0;--i)
nums[i+k]=nums[i];
for(int i=0;i<k;++i)
nums[i]=helper[i];
}
};
黔驴技穷,题解就是比我想的花里胡哨的多。
方法一:使用额外数组
依旧使用这个例子:
nums:[1, 2, 3, 4, 5, 6, 7]
右旋转3位后得到:[5, 6, 7, 1, 2, 3, 4]
即要将旋转后对应的原数组下标:[4, 5, 6, 0, 1, 2, 3] 转换成新数组的正确下标:[0, 1, 2, 3, 4, 5, 6]
那就比较明显了,(原数组各位下标值 + k) % 数组长度即可。
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int n = nums.size();
vector<int> newArr(n);
for (int i = 0; i < n; ++i)
newArr[(i + k) % n] = nums[i];
nums.assign(newArr.begin(), newArr.end());
}
};
方法二:环状替换
这个本来想略略略,怕烦qwq。
和方法一中的思想类似,省略构建一个同样大小的额外数组的部分。
从位置 0 开始,直接借助变量 temp 交换 下标 0 与下标 (0 + k)% n 对应的数组值,令 x = (0 + k)% n,下一个交换下标 x 与下标 (x + k)% n 对应的数组值直至回到初始位置 0。
nums : [1, 2, 3, 4, 5, 6],k = 2 时,结果应该为:[3, 4, 5, 6, 1, 2]
如上述方法:
交换位置 0:[3, 2, 1, 4, 5, 6] --> [3, 2, 5, 4, 1, 6],回到初始位置。
交换位置 1:[3, 4, 5, 2, 1, 6] --> [3, 4, 5, 6, 1, 2],回到初始位置。
如下图,完成了整个交换。
最小公约数那个我就不强求自己理解了,使用单独的变量 count 跟踪当前已经访问的元素数量,当 count=n 时,结束遍历过程。
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int n = nums.size();
k = k % n;
int count = 0; //计算当前已经访问的元素数量
int start = 0; //起始点
while(count<n){ //当访问数量小于数组个数时
int current = start;
int prev = nums[start];
//++count; //这里原本是想选中一个数就计数(后面循环每次两两比较额外多加一个访问的数量,但是下面循环是do..while结构,最后执行到start==current退出循环,这一步不计数,下面就多算了一个++count,所以把上面的屏蔽掉就对了)
do {
int next = (current + k) % n;
swap(nums[next], prev);
current = next;
++count;
} while (start != current);
++start;
}
}
};
方法三:数组翻转
这个就好理解多了,感觉这个也和我本身思路最像。
操作 | 结果 |
---|---|
原始数组 | 1 2 3 4 5 6 7 |
翻转所有元素 | 7 6 5 4 3 2 1 |
翻转 [0, k mod n-1] 区间的元素 | 5 6 7 4 3 2 1 |
翻转 [k mod n, n-1] 区间的元素 | 5 6 7 4 3 2 1 |
class Solution {
public:
void reverse(vector<int>& nums, int start, int end) {
while (start < end) {
swap(nums[start], nums[end]);
start += 1;
end -= 1;
}
}
void rotate(vector<int>& nums, int k) {
k %= nums.size();
reverse(nums, 0, nums.size() - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, nums.size() - 1);
}
};