给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/rotate-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
笔记:链接(力扣)
一、使用额外的数组
用 n 表示数组的长度,我们遍历原数组,将原数组下标为 i 的元素放至新数组下标为 (i+k) mod n 的位置,最后将新数组拷贝至原数组即可。
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());
}
};
时间复杂度: O(n),其中 n 为数组的长度。
空间复杂度: O(n)。
注1:谨记 mod 这个概念和方法!
注2:assign() 函数原型:
1:void assign(const_iterator first,const_iterator last); //相当于拷贝函数,把 first 到 last 的值赋值给调用者(注意区间的闭合)
2:void assign(size_type n,const T& x = T()); //把 n 个 x 赋值给调用者
二、环装替换
方法一中使用额外数组的原因在于如果我们直接将每个数字放至它最后的位置,这样被放置位置的元素会被覆盖从而丢失。因此,从另一个角度,我们可以将被替换的元素保存在变量 temp 中,从而避免了额外数组的开销。
我们从位置 0 开始,最初令 temp=nums[0]。根据规则,位置 0 的元素会放至 (0+k) mod n 的位置,令 x=(0+k) mod n,此时交换 temp 和 nums[x],完成位置 x 的更新。然后,我们考察位置 x,并交换 temp 和 nums[(x+k) mod n],从而完成下一个位置的更新。不断进行上述过程,直至回到初始位置 0。
容易发现,当回到初始位置 0 时,有些数字可能还没有遍历到,此时我们应该从下一个数字开始重复的过程,可是这个时候怎么才算遍历结束呢?我们不妨先考虑这样一个问题:从 0 开始不断遍历,最终回到起点 0 的过程中,我们遍历了多少个元素?
由于最终回到了起点,故该过程恰好走了整数数量的圈,不妨设为 a 圈;再设该过程总共遍历了 b 个元素。因此,我们有 an=bk,即 an 一定为 n,k 的公倍数。又因为我们在第一次回到起点时就结束,因此 a 要尽可能小,故 an 就是 n,k 的最小公倍数 lcm(n,k),因此 b 就为 lcm(n,k)/k。
这说明单次遍历会访问到 lcm(n,k)/k 个元素。为了访问到所有的元素,我们需要进行遍历的次数为
其中 gcd 指的是最大公约数。
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int n = nums.size();
k = k % n;
int count = gcd(k, n);
for (int start = 0; start < count; ++start) {
int current = start;
int prev = nums[start];
do {
int next = (current + k) % n;
swap(nums[next], prev);
current = next;
} while (start != current);
}
}
};
时间复杂度:O(n),其中 n 为数组的长度。每个元素只会被遍历一次。
空间复杂度:O(1)。我们只需常数空间存放若干变量。
三、数组翻转
我们可以先将所有元素翻转,这样尾部的 k mod n 个元素就被移至数组头部,然后我们再翻转 [0, (k mod n)−1] 区间的元素和 [k mod n, n−1] 区间的元素即能得到最后的答案。
class Solution {
public:
void reverse(vector<int>& nums, int start, int end) {
while (start < end) {
swap(nums[start], nums[end]); //直接使用swap函数!!
start += 1;
end -= 1;
}
}
void rotate(vector<int>& nums, int k) {
k %= nums.size(); //mod n
reverse(nums, 0, nums.size() - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, nums.size() - 1);
}
};
时间复杂度:O(n),其中 n 为数组的长度。每个元素被翻转两次,一共 n 个元素,因此总时间复杂度为 O(2n)=O(n)。
空间复杂度:O(1)。
总结:核心问题还是在于 mod 的使用,只理所当然地考虑了 k<n 的情况,算法健壮性不够。