轮转数组
今天的题目是力扣面试经典150题中的数组的基础难度题:轮转数组。
题目链接:https://leetcode.cn/problems/rotate-array/description/?envType=study-plan-v2&envId=top-interview-150
问题描述
给定一个整型数组 nums 和一个整数 k,将数组中的元素向右轮转 k 个位置。
例如,给定数组 [1,2,3,4,5,6,7] 和 k = 3,轮转后的数组应该是 [5,6,7,1,2,3,4]。
-
示例
- 输入:
nums = [1,2,3,4,5,6,7]
k = 3 - 输出:
[5,6,7,1,2,3,4]
- 输入:
-
示例:
- 输入:
nums = [-1,-100,3,99]
k = 2 - 输出:
[3,99,-1,-100]
- 输入:
题目分析
题目要求能够将一个整型数组中的所有元素向右轮转 k 个位置。
先根据题目中的示例,理解一下轮转的含义。
观察示例可以发现,轮转意味着数组的最K个元素会按原有的顺序被移动到数组的最前面,而数组前面的元素则相应地向后移动。
解题思路
题目有多种解题思路,我们一个个来。
复制替换法
这个问题第一时间想到的方法是新建一个数组,将先将目标K个元素放入到新数组中,然后根据轮转的要求将原数组中的元素复制到新数组的正确位置上。最后将新数组的内容复制回原数组。这是方法一,我们叫它复制替换法。
在解答数组题目时,我们通常考虑不使用额外空间,也就是不新建数组的方式,所以我们再思考一下,怎么能在原数组上操作实现轮转。
反转数组法
轮转的意义是将后面K个元素放到前面,前面的元素放到后面,同时保证顺序不变。同时,我们不想操作额外的空间,就在原数组上操作。那么我们是不是可以先将整个数组反转,这个时候是不是相当于前面的元素放到了后面,后面的元素放到了前面,这个时候我们只需要考虑得到是,前k个元素需要按原来的位置摆放,后n-k个元素也按原来的位置摆放就行?
这两部分的元素如何恢复原有的顺序呢?还是反转,正所谓负负得正,我们反转得到的数组,再反转不就回到之前吗?
这样,先反转整体,在分别反转两部分的方法,我们叫它反转数组法,这是第二种方法。
实际算法代码
根据上面分析的两种方法,我们可以编写如下代码:
java
public class Solution {
public static void main(String[] args) {
Solution solution = new Solution();
int[] nums = {1, 2, 3, 4, 5, 6, 7};
int k = 3;
solution.rotate(nums, k);
solution.rotate2(nums, k);
for (int num : nums) {
System.out.print(num + " ");
}
}
public void rotate(int[] nums, int k) {
int n = nums.length;
k %= n; // 防止k大于数组长度的情况
int[] result = new int[n];
for (int i = 0; i < n; i++) {
result[(i + k) % n] = nums[i];
}
for (int i = 0; i < n; i++) {
nums[i] = result[i];
}
}
public void rotate2(int[] nums, int k) {
int n = nums.length;
k %= n; // 处理 k 大于数组长度的情况
reverse(nums, 0, n - 1); // 反转整个数组
for (int num : nums) {
System.out.print(num + " ");
}
System.out.println();
reverse(nums, 0, k - 1); // 反转前 k 个元素
for (int num : nums) {
System.out.print(num + " ");
}
System.out.println();
reverse(nums, k, n - 1); // 反转剩余的元素
for (int num : nums) {
System.out.print(num + " ");
}
System.out.println();
}
private void reverse(int[] nums, int start, int end) {
while (start < end) {
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
start++;
end--;
}
}
需要注意,两个函数不能同时调用,因为第一个函数过后,原数组已经被轮转了,第二个函数轮转的第一个轮转以后的数组。
结果
执行函数,测试通过:
图片是使用反转法输出,可以看到原数组被一步步反转成了目标数组。
提交到力扣,也正常通过:
总结
根据我们对函数的分析以及力扣提交后的结果,我们发现:
- 复制替换法使用了额外的空间,时间复杂度为 O(n),空间复杂度为 O(n)。这种方法简单易懂,适合初学者理解轮转的概念。
- 反转数组法在原地完成轮转操作,时间复杂度为 O(n),空间复杂度为 O(1)。这种方法虽然实现稍微复杂一点,但更节省空间。
力扣中还有最一个循环设置法,我没有想到,看了一下实现。简单的说就是一个个元素循环去放置。
这个方法比较复杂,但是一步步去debug观察元素的移动,可以很大程度上帮助新手理解数组的交换与移动,感兴趣的可以自己实现一下。
加油!!!