题目:
Given an array, rotate the array to the right by k steps, where k is non-negative.
Example 1:
Input:[1,2,3,4,5,6,7]
and k = 3 Output:[5,6,7,1,2,3,4]
Explanation: rotate 1 steps to the right:[7,1,2,3,4,5,6]
rotate 2 steps to the right:[6,7,1,2,3,4,5]
rotate 3 steps to the right:[5,6,7,1,2,3,4]
Example 2:
Input: [-1,-100,3,99]
and k = 2
Output: [3,99,-1,-100]
Explanation:
rotate 1 steps to the right: [99,-1,-100,3]
rotate 2 steps to the right: [3,99,-1,-100]
Note:
- Try to come up as many solutions as you can, there are at least 3 different ways to solve this problem.
- Could you do it in-place with O(1) extra space?
这道题是要右旋给定的数组k位,相当于把最右边的k个数接到数组的头部。在自己想的时候也只能想到新开辟一个数组用来存放转换后的数组的方法,并且自己写代码的时候还写错了,下面说一下第一次自己写的代码的出错点:
1. 题意理解不到位,以为如果k比数组的长度还大就直接返回原始数组,结果是可以疯狂旋转的,也就是可以选转nums.size()的整数倍次后回到原始位置继续转。
2. 没有想到可以直接取模,而是先把要旋转的取出来,再去取原始顺序的,这也间接导致了取下标的时候会遇到k和nums.size()的大小比较的问题。
后来看了solutions才意识到可以直接先定好新数组的大小,然后对新数组的下标取模来计算新位置,真的非常方便简洁。
代码如下(注释里是之前自己写的错的代码,但感觉把第一个if里面的return改成k %= nums.size()应该也可以),时间复杂度O(n),16ms,95.14%,空间复杂度O(n),9.6M,28.48%:
/*
* @lc app=leetcode id=189 lang=cpp
*
* [189] Rotate Array
*/
class Solution {
public:
void rotate(vector<int>& nums, int k) {
/*if (k > nums.size()) {
return;
}
vector<int> result;
for (int i = k; i > 0; i--) {
result.push_back(nums[nums.size()- i]);
}
for (int i = 0; i < nums.size() - k; i++) {
result.push_back(nums[i]);
}*/
vector<int> result(nums.size(), 1);
for (int i = 0; i < nums.size(); i++) {
result[(i + k) % nums.size()] = nums[i];
}
nums = result;
}
};
2. 反转法
这个方法真是太巧妙了!先把整个数组反转过来,然后再分别对前k个和后nums.size()-k个进行反转,就能得到最终的答案!一看就懂但是自己就是想不到哎。另外,也可以先分别对前nums.size()-k和后k个进行反转,然后再整体反转,都是一个道理,但我觉得先反转会更直观一些吧。
代码如下,时间复杂度O(n),16ms,95.14%,空间复杂度O(1),9.6M,32.08%:
/*
* @lc app=leetcode id=189 lang=cpp
*
* [189] Rotate Array
*/
class Solution {
public:
void rotate(vector<int>& nums, int k) {
k %= nums.size();
reverse(nums.begin(), nums.end());
reverse(nums.begin(), nums.begin() + k);
reverse(nums.begin() + k, nums.end());
}
};
3. 循环旋转法
这个方法证明起来稍微有一点点复杂,刚开始看lc自带的solution都没看懂,看了discussion又自己在纸上画了一下才看明白,觉得有点眼熟但是想不起来了。前面两种方法已经是三个月前写的了,时隔三个月我终于又开始刷题了。
这个方法的focus在待旋转的元素上,从第一个元素开始,先把第一个元素放到合适的位置,被替换掉的元素作为下一个待旋转的元素,如此循环到最后,待旋转的元素的位置总会回到最初的起点。然而,这里还不一定结束了,因为可能存在要旋转的个数k正好能被数组的长度整除的情况,这样的话可能从头开始循环一次就回到起点,而其他位置的元素并不能被移动,那么要如何判断是不是已经把所有元素都旋转完了呢?这时候就可以联系到旋转数组需要对每个元素都进行一次操作的特性,也就是说一定要旋转够元素长度的次数才算结束,因此可以把它作为结束条件。
最开始自己写代码的时候因为不是很习惯用while循环就直接写了最外层的for循环,搞了半天一直都是overflow的错误不知道哪儿错了,后来又print出来仔细想了一下发现我是把以每个元素为起始都检查了一遍,而这里只需要全部旋转一次就够了,所以不能使用for循环,要用while加上在内层循环里count++来及时停止循环。另外还有一点比较坑的就是,内层循环中刚开始也是不习惯用do-while语句就直接写了while(current != start),结果发现output出来和原始的数组一样,才发现由于最开始current是和start相等的,所以一直都不会执行。还有一个坑就是以下代码中do前面的那一句,本来放在current++和start++后面的,结果在submit的时候一直过不去[1], 1的test case,发现是current++之后就下标就溢出了,于是把它放到了最前面,并不影响具体功能。
具体完整都代码如下,时间复杂度O(n),16ms,85.01%,空间复杂度O(1),9.6M,55.56%:
class Solution {
public:
void rotate(vector<int>& nums, int k) {
if (nums.size() == 0 || k == 0) {
return;
}
int size = nums.size();
int start = 0;
int current = 0;
int numToBeRotated = nums[0];
int count = 0;
while (count < size) {
numToBeRotated = nums[current];
do {
int newIndex = (current + k) % size;
int temp = nums[newIndex];
nums[newIndex] = numToBeRotated;
current = newIndex;
numToBeRotated = temp;
count++;
} while (start != current);
start++;
current++;
}
}
};
另外在https://leetcode.com/problems/rotate-array/discuss/54277/Summary-of-C%2B%2B-solutions 里还有两种方法,但是感觉有一点点复杂而且现在又赶时间所以就决定暂时放弃了,以后有机会再回来研究。