原题链接: https://leetcode.com/problems/rotate-array/
1. 题目介绍
Given an array, rotate the array to the right by k steps, where k is non-negative.
给出一个一维数组和一个非负整数k,将数组向右循环移动 k 位。
输出移动后的数组。
注意,题目中有一个隐含的条件没有说出来,那就是k可能大于数组的长度。
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?
至少有三种方法可以解决这个问题。可以尝试空间复杂度为O(1)的方法吗?
2. 解题思路
方法1 - 整体右移k步
首先,k可能大于数组的长度,因此需要取 k = k % length,length是数组的长度。
如果想要采用空间复杂度为 O(1) 的做法,可以将最右边的元素保留下来,然后将所有元素右移一位,再把保留下的值赋值给最左边的元素。这样重复 k 步,就相当于全体右移了k位。
这样虽然空间复杂度为 O(1) ,但是时间复杂度却是 O(n*k)
实现代码
class Solution {
public void rotate(int[] nums, int k) {
int length = nums.length;
if(length == 0) {
return;
}
k = k%length;
int temp = 0;
for(int i = 1 ; i<=k ; i++) {
temp = nums[length-1];
for(int j = length-1 ; j>=1 ; j-- ) {
nums[j] = nums[j-1];
}
nums[0] = temp;
}
return;
}
}
在LeetCode的官方题解 中,还给出了一些其他的解法,下面对他们进行一个总结:
注: 以下的思路来自于 https://leetcode.com/problems/rotate-array/solution/
方法2 - 使用额外的数组
下面实现的代码的亮点在于它只用了一个for循环就完成了原来数组循环放入新数组的过程。它的规律在于旧数组的第 i 个元素,总会放到新数组的第 (i + k) % nums.length 位置上。实现的关键语句是
时间复杂度:O(n).
空间复杂度:O(n).
a[(i + k) % nums.length] = nums[i];
实现代码
public class Solution {
public void rotate(int[] nums, int k) {
int[] a = new int[nums.length];
for (int i = 0; i < nums.length; i++) {
a[(i + k) % nums.length] = nums[i];
}
for (int i = 0; i < nums.length; i++) {
nums[i] = a[i];
}
}
}
方法3 - 循环替代
这个方法非常巧妙,是依靠循环的特性实现的。之前所讲的方法,都是非常“宏观”的方法,是根据整体变换完的结果来改变数组的值。但是如果我们另辟蹊径,完全模拟一个每个数的移动过程,是不是也可以解决这个问题呢?
我们以下面这个例子来说明:
nums: [1, 2, 3, 4, 5, 6]
k = 2
因为k = 2,所以,1到3,3到5,5又会去1。{1、3、5} 这三个数会形成一个循环。我们就可以模拟这个过程。每次我们替换数字的时候,都需要先用一个变量保存被替换的数字,然后再用这个保存起来的变量去替换下一个数字。
首先,把3的值保留下来,然后1替换3
然后,把5的值保留下来,然后利用上一步保留的3,替换5
最后,把1的值保留下来,然后利用上一步保留的4,替换1
这时候,我们发现又回到最初开始的位置。于是结束{1,3,5}这个循环,来到2的位置,开始{2,4,6}的循环。
值得注意的是,{1、3、5}循环中的数字,下标 i % k 都等于0;{2、4、6}循环中的数字,下标 i % k 都等于1;
( 上图出处: https://leetcode.com/problems/rotate-array/solution/ )
什么时候结束模拟呢?如果数组中有n个数,那么我们模拟每个数的过程,就需要替换n次。于是每做1次替换,我们都对替换的次数+1,当替换的次数为 n 的话,就说明所有的数都已经移动完了,可以结束模拟了。
再举一个例子,
nums: [1, 2, 3, 4, 5, 6,7]
k = 2
模拟每一个数的移动过程,可以发现大概是这样的一个过程:
这次只有一个大的循环{1,3,5,7,2,4,6}。当替换的次数为7时,此时模拟结束。同时也恰好回到了最初开始的位置(nums[0]的位置)。
这样的方法,时间复杂度O(n),空间复杂度O(1)
为什么时间复杂度会是O(n)呢?因为这个方法的本质是模拟每一个数的移动,一共只有n个数,那就只会模拟n次,因此时间复杂度就是O(n)。
实现代码
class Solution {
public void rotate(int[] nums, int k) {
int length = nums.length;
if(length == 0) {
return ;
}
k = k % length;
int Count = 0;
for(int i = 0 ; i < length ; i++) {
int current = i;
int templeft = nums[i];
int tempright = 0;
do {
int next = ( current + k ) % length;
tempright = nums[next];
nums[next] = templeft;
templeft = tempright;
current = next;
Count ++;
}while(i != current);
if( Count == length) {
break;
}
}
return;
}
}
方法4 - 反转数组
首先让 k = k % length。 当我们将数组右移 k 步的时候,数组最后的 k 个数字会移到数组的最前面来。
于是我们可以首先反转数组中的全部元素,然后反转前 k 个数字,再反转后面的 n-k 个数字。
比如 n=7 , k=3 时:
最初的数组: 1 2 3 4 5 6 7
反转全部元素: 7 6 5 4 3 2 1
将前k个元素反转: 5 6 7 4 3 2 1
将后面n-k个元素反转: 5 6 7 1 2 3 4 这就是最终的结果了。
时间复杂度: O(n)
空间复杂度: O(1)
实现代码
public class Solution {
public void rotate(int[] nums, int k) {
k %= nums.length;
reverse(nums, 0, nums.length - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, nums.length - 1);
}
public void reverse(int[] nums, int start, int end) {
while (start < end) {
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
start++;
end--;
}
}
}