LeetCode#189. 轮转数组
这段时间由于发现自己算法题很久没做过了,决定重新开始做一些这些算法题锻炼一下······,后续类似的个人感觉比较有意思的算法题,后续估计都会发到 blog 上 (比较简单的就不发了)
题目如下:
/*
* 给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
* 示例 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 <= 10^5
* -2^31 <= nums[i] <= 2^31 - 1
* 0 <= k <= 105
*
* 进阶:
* 尽可能想出更多的解决方案,至少有 三种 不同的方法可以解决这个问题。
* 你可以使用空间复杂度为 O(1) 的 原地 算法解决这个问题吗?
*/
之前在做这道题的时候,发现不考虑空间复杂度其实问题相当简单,只需要截断数组然后拼接即可。但是如果只考虑空间复杂度是 O(1) 的方案的话,那么问题就会复杂很多。
解决思路
不难看出,如果我们不能新建任何数组来解决 (自然不能截断数组) 的话,那么我们只能通过交换的方式来解决这个位置偏移的问题。而具体的偏移要怎么写,有点无从下手,最简单的方式当然是搞几个例子看一下。
例子1: nums = [1,2,3,4,5,6,7], k = 3
显然最终目标是:[5,6,7,1,2,3,4],如果完全通过 swap 交换 (被交换结果的值,因为在 temp 中,因此必须在下一次交换中处理) 来完成,那么实际上需要做 7 次交换来完成。
index:
0 -> 3
3 -> 6
6 -> 2
2 -> 5
5 -> 1
1 -> 4
4 -> 0
看起来似乎直接等于 arr.length ,当然只有一个 demo 来看肯定是不行的,我们继续找几个 demo 看看。
例子2:nums = [-1,-100,3,99], k = 2
显然需要的结果是:[3,99,-1,-100],在完全通过 swap 交换的方式来完成处理的话,那么实际上需要 4 次交换。
index:
0 -> 2 1 -> 3
2 -> 0 3 -> 1
但是跟例子1对比,显然有所不同,要完成两次交换回环,我们才能完成整个目标数组的偏移工作。从这个例子我们已经可以很明显的看出来,实际上这道题必然是在 m 个交换回环中,通过 arr.length 次交换来完成偏移。
交换回环的 m 到底是怎么算出来的?
从现在的信息来看,似乎还不太能推导出这个 m 到底是什么?最快的方法当然是继续多写两个示例,看规律。
例子3:nums = [1,2,3,4,5,6] k = 4
需要的结果是:[3,4,5,6,1,2] ,当然,在完全通过 swap 交换的方式来完成处理的话,那么实际上需要 6 次交换。
index:
0 -> 4 1 -> 5
4 -> 2 5 -> 3
2 -> 0 3 -> 1
这个 demo 回环是 2,,而回环中交换数为 3。
例子4: nums = [1,2,3,4,5,6] k = 3
需要的结果是:[4,5,6,1,2,3,]
index:
0 -> 3 1 -> 4 2 -> 5
2 -> 0 4 -> 1 5 -> 2
在这个 demo 之中,我们的交换回环直接变成了 3,而回环中交换数为 2 。很明显这个是一个非常关键的信息。
Summary
显然,这个回环的个数,必然是取决于 arr.length 和 k 的取值的,这一点从 例子3 和 例子4 就能看出来。
如果 arr.length 是 7 ,k 是 3 那么回环是 1,回环交换数 7
如果 arr.length 是 4 ,k 是 2 那么回环是 2,回环交换数 2
如果 arr.length 是 6 ,k 是 4 那么回环是 2,回环交换数 3
如果 arr.length 是 6 ,k 是 3 那么回环是 3,回环交换数 2
······
从上面这个规律,我们可以得到一个公式,它的本质意思也很好理解,其实就是在计算 经过多少次长度为 k 的元素偏移,才能完整走完整个 arr 中元素,也就是 length 。
既然 也就是回环交换数为 n ,我们可以进一步演变这个公式变为 -> n * k % length = 0,用以快速计算回环交换数 / 回环数
n * 3 mod 7 = 0 -> 显然只有 n = 7 的时候,才能满足该条件
n * 2 mod 4 = 0 -> 显然只有 n = 2 的时候,才能满足该条件
······
通过这个公式获得具体的 回环数 / 回环交换数 之后,我们就可以以 回环数 作为循环次数,内部只需要确保回到交换的起点即可。而显然的,如果我们要算出来这两个的值,必然需要知道 k 和 arr.length 两者的最小公倍数。
// 最大公约数
public static int gcd(int a, int b) {
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
// 根据最大公约数算出两者的最小公倍数
public static int lcm(int a, int b) {
return a * (b / gcd(a, b));
}
将我们的最小公倍数代入公式,便可轻松得到我们一直寻求的,回环数和回环交换数。
Answear
class Solution {
public static int gcd(int a, int b) {
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
public static int lcm(int a, int b) {
return a * (b / gcd(a, b));
}
public void rotate(int[] nums, int k) {
if ( k == 0 )
return;
else {
int lcm = lcm(nums.length, k);
// 算出需要几次循环
int count = nums.length / (lcm / k);
for (int i = 0; i < count; i++) {
exchange(nums, k, i);
}
}
}
private static void exchange(int[] nums, int k, int start) {
int end = (start + k) % nums.length;
int temp = nums[start];
int temp1;
while ( end != start ){
temp1 = nums[end];
nums[end] = temp;
temp = temp1;
end = (end + k) % nums.length;
}
nums[end] = temp;
}
}