力扣热题100道之189轮转数组
解法
这道题我用了四个半点的时间做出来。最开始找的那种方法,不到一个点就做出来了,后面的思路有些难以理清。从这道题目中我受益匪浅,没有掌握的知识点有dowhile的用法,还有找到两个数的最大公约数的方法。
class Solution {public void rotate(int[] nums, int k) {int n = nums.length;k=k%n;int c=gcd(n,k);for(int i=0;i<c;i++){int curr=i;int pre=nums[i]; do{int next=(curr+k)%n;int tmp=nums[next];nums[next]=pre;pre=tmp; curr=next; }while(curr!=i);}}public int gcd(int x,int y){return y>0?gcd(y,x%y):x;}}
找到两个数的最大公约数的方法
代码如下,之后可以直接定义这个方法,然后直接用就可以,后面有解释的地方,可以帮助理解
public int gcd(int x,int y){return y>0?gcd(y,x%y):x;
}
1. 算法历史与基本思想
欧几里得算法是历史上最古老的算法之一,出现在欧几里得的《几何原本》(公元前300年左右)。它的目的是求两个整数的最大公约数。
最大公约数:能同时整除两个数的最大正整数。
例如:gcd(12, 18) = 6,因为6是能整除12和18的最大整数。
2. 算法核心原理
关键定理:
如果 a = b * q + r
(其中 q
是商,r
是余数,且 0 ≤ r < b
),那么:
gcd(a, b) = gcd(b, r)
为什么成立?
- 设
d = gcd(a, b)
,那么d
能整除a
和b
- 因为
r = a - b * q
,所以d
也能整除r
- 因此
d
是b
和r
的公约数 - 同理,任何能整除
b
和r
的数也能整除a
- 所以
gcd(a, b) = gcd(b, r)
3. 数学推导示例
手动计算 gcd(48, 18)
:
48 ÷ 18 = 2 余 12 → gcd(48, 18) = gcd(18, 12)
18 ÷ 12 = 1 余 6 → gcd(18, 12) = gcd(12, 6)
12 ÷ 6 = 2 余 0 → gcd(12, 6) = 6
另一个例子 gcd(1071, 462)
:
1071 ÷ 462 = 2 余 147 → gcd(1071, 462) = gcd(462, 147)
462 ÷ 147 = 3 余 21 → gcd(462, 147) = gcd(147, 21)
147 ÷ 21 = 7 余 0 → gcd(147, 21) = 21
历史解法
分别开辟了两个总长为n的空间的解法
首先,关于这道题的理解,当k<n的时候,也就是题目描述中给出的那些情况,将后半段和前半段分别存储到一个第三方空间中,再分别复制到nums数组中。当k>n的时候,k=k%n,他是这么算的,这个题目中也没有明说,感觉要是这点他明说的话,可以省去好多时间。
class Solution {public void rotate(int[] nums, int k) {int n=nums.length;if(n!=1&&n>=k){int []tmp=new int[k];int[]t=new int[n-k];for(int i=n-k;i<n;i++){tmp[i-n+k]=nums[i];}for(int i=0;i<n-k;i++){t[i]=nums[i];}for(int i=0;i<k;i++){nums[i]=tmp[i];}for(int i=0;i<n-k;i++){nums[i+k]=t[i];}}else{k=k%n;int []tmp=new int[k];int[]t=new int[n-k];for(int i=n-k;i<n;i++){tmp[i-n+k]=nums[i];}for(int i=0;i<n-k;i++){t[i]=nums[i];}for(int i=0;i<k;i++){nums[i]=tmp[i];}for(int i=0;i<n-k;i++){nums[i+k]=t[i];}}}
}
优化上面的那个方法
我发现那个条件判断语句可以单独列在前面。
class Solution {public void rotate(int[] nums, int k) {int n=nums.length;if(n!=1&&n>=k){k=k;}else{k=k%n;}int []tmp=new int[k];int[]t=new int[n-k];for(int i=n-k;i<n;i++){tmp[i-n+k]=nums[i];}for(int i=0;i<n-k;i++){t[i]=nums[i];}for(int i=0;i<k;i++){nums[i]=tmp[i];}for(int i=0;i<n-k;i++){nums[i+k]=t[i];}}
}
目标空间复杂度为O(1)但失败的方法
这个方法,我想通过每次直接找到一个数据的准确位置,下一次直接找到被替换的数据的正确位置,这样就可以直接找到所有值的正确位置了。但是最后我发现有一个问题,我没有解决,就是当满足一些条件比如n==2k再比如n=6,k=2(可能是当n可以整除k)的时候,会来回就更新那几个数字,跑不到别的数字上面,就搞得很难受。我觉得再有一些时间我肯定能解决这个问题,但是,这道题我已经看了两个半点了,再看有些不划算了。所以,我准备直接看答案。看这种最优解法。
class Solution {public void rotate(int[] nums, int k) {int n = nums.length;if (n != 1 && n >= k) {k = k;} else {k = k % n;}int count = 0;int i = 0;int tmp;int t = nums[0];if (n != 2 * k) {while (count < n) {if (n == 2 && k == 2) {break;}int l=n-k-1;if (i <= l) {tmp = nums[i + k];nums[i + k] = t;t = tmp;i = i + k;} else {tmp = nums[i - n + k];nums[i - n + k] = t;t = tmp;i = i - n + k;}count++;}} else {for (int j = 0; j < k; j++) {tmp = nums[j];nums[j] = nums[j + k];nums[j + k] = tmp;}}}}
对上面方法的优化
我看题解,看到了找下一个的一个好方法(i+k)%n。这个方法可以直接少了很多条件判断的语句,代码量少了一半,效果还差不多,但是那个陷入局部走不出来的方法还是没有解决。
class Solution {public void rotate(int[] nums, int k) {int n = nums.length;if (n != 1 && n >= k) {k = k;} else {k = k % n;}int count = 0;int i = 0;int tmp;int t = nums[0];if(n!=2*k){while (count < n) {tmp = nums[(i + k)%n];nums[(i + k)%n] = t;t = tmp;i = (i + k)%n; count++; }}else{for (int j = 0; j < k; j++) {tmp = nums[j];nums[j] = nums[j + k];nums[j + k] = tmp;} }}}
因为while()和do...while()的区别而报错的方法
我看了题解,但是一直做不出来,我总是想如果我完全理解了题解中的方法,那么我不要跟他的方法完全一样。
class Solution {public void rotate(int[] nums, int k) {int n = nums.length;k=k%n;int c=gcd(n,k);for(int i=0;i<c;i++){int curr=i;int pre=nums[i]; while(curr!=i){int next=(curr+k)%n;int tmp=nums[next];nums[next]=pre;pre=tmp; curr=next; }}}public int gcd(int x,int y){return y>0?gcd(y,x%y):x;}}