Leetcode–轮转数组【189】
#题目
题目描述:给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
提示:以下是本篇文章正文内容,下面案例可供参考
一、模拟方法
模拟方法,通过计算每一个元素的新位置,进行赋值(多开一组空间),公式如下:
根据求余运算公式的分配律:
更新公式:
其中old_ID必然小于等于N(数组长度),K则未必,当K是N的整倍数时,则无需计算移动,通过复制数组,代码如下:
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int lens = nums.size();
if(k % lens !=0 ){
int kk= k % lens;
vector<int> tmp(lens,0);
//copy nums
for (int i =0; i<lens; i++){
tmp[i] = nums[i];
}
//
for(int i =0; i<lens; i++){
int new_i = 0;
new_i = (i + kk) % lens;
nums[new_i] =tmp[i];
}
}
}
};
时间复杂度O(N),空间O(N),因为用到了复制。
二、遍历
针对第一种的空间复杂度, 由于移动是连续的,a1移动到a2(a1+kk)mod n, a2必定会移动到a3(a2+kk) mod n, 因此可以仅保留待移动变量, 使得空间复杂度降低为O(1)。
但这里有一个坑, 就是当KK=1时, 这么移动可以覆盖每一个数组元素, 当KK为其他数值时,可能会漏掉某些元素。
当从start起点开始, 再次回到start时,跳了b步,最少进行了 a圈(不一定是1圈),走过了an=bk个长度, an = bk, an也是 n, k的最小公倍数,b=lcm(n,k)/k。 每挑一步,就是一次访问, 所以单次遍历(回到起点)会访问b个元素, 还需要n/b次,才能访问完所有元素。
做这种回到起点的遍历,需要x次, x = nk/lcm(n,k) = gcd(n,k)-----最小公倍数lcm , 最大公约数gcd
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int lens = nums.size();
if(k % lens !=0 ){
// 求余
int kk = k %lens;
int move_count = gcd(kk, lens);
for(int start =0; start < move_count; ++start){
int current_id = start;
int prev_number = nums[start];
// do-while 先做后判断,while 先判断再 做
do{
int next_id = (current_id + kk )% lens;
swap(nums[next_id], prev_number);
current_id = next_id;
}while(start != current_id );
}
}
}
};
三、翻转数组
.翻转数组的原理,如下图所示:
相应的代码如下:
class Solution {
public:
void reverseArrary(vector<int>& arr, int begin, int end){
int tmp = 0;
int left = begin, right = end;
while(left < right){
tmp = arr[left];
arr[left] = arr[right];
arr[right] = tmp;
left++;
right--;
}
}
//重点是K>length时的处理,要求余,整除不移动。
void rotate(vector<int>& nums, int k) {
int lens = nums.size();
if(k % lens !=0 ){
int kk= k % lens;
reverseArrary(nums, 0, lens-1);
reverseArrary(nums, 0, kk-1);
reverseArrary(nums, kk, lens-1);
}
// return nums
}
};
总结
整体上有模拟的思路->空间优化->寻找特殊规律。 个人感觉套用代码模板、公式,这种解题思路会更清晰,复用性更强。