四种方法解决——力扣189.轮转数组
力扣189.轮转数组
数组轮转题目全解析:四种方法解决 Rotate Array(含 Java 实现与复杂度分析)
摘要
本文详细解析了 LeetCode 热门题目「旋转数组」(Rotate Array)。我们将从直观到高效介绍四种解法:暴力法、额外数组法、翻转法、环状替换法,逐一讲解思路、优缺点,并给出完整的 Java 实现与复杂度分析。最后总结推荐解法及面试答题策略,帮助读者快速掌握该题。
正文
一、题目描述
给定一个整数数组 nums
,将数组中的元素向右轮转 k
个位置,其中 k
是非负数。
示例:
输入: 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]
提示:
- 1 <= nums.length <= 10^5
- -2^31 <= nums[i] <= 2^31 - 1
- 0 <= k <= 10^5
进阶:
请尽可能提出多种解决方案,并实现原地 O(1) 空间的算法。
二、思路分析
- 暴力法:每次右移 1 步,重复 k 次,直观但效率低。
- 额外数组法:借助临时数组存储旋转结果,再拷贝回原数组,时间 O(n),空间 O(n)。
- 翻转法:通过三次反转(整体翻转 + 前 k 个翻转 + 后 n-k 个翻转)实现原地 O(1) 解法,是推荐方案。
- 环状替换:基于元素下标循环移动,时间 O(n),空间 O(1),实现稍复杂,但能展示数学思路。
三、解法一:暴力法
思路:
每次将最后一个元素取出,插入到数组最前端,其他元素整体后移一位,重复 k 次。
代码实现:
public void rotateBruteForce(int[] nums, int k) {int n = nums.length;for (int step = 0; step < k; step++) {// 每一步将最后一个元素移动到最前面,其余右移一位int last = nums[n - 1];for (int i = n - 1; i > 0; i--) {nums[i] = nums[i - 1];}nums[0] = last;}}
复杂度:
- 时间复杂度:O(n·k)
- 空间复杂度:O(1)
缺点:当 n 和 k 很大时效率过低。
四、解法二:额外数组法
思路:
将数组元素按照旋转后的顺序放入一个新数组,再复制回原数组。
代码实现:
public void rotateWithExtraArray(int[] nums, int k) {int n = nums.length;int[] tmp = new int[n];// 将 nums 的最后 k 个放在最前面,其他顺序跟随int start = n - k; // 从 nums[start] 开始复制到 tmp[0]for (int i = 0; i < n; i++) {tmp[i] = nums[(start + i) % n];}// 拷贝回原数组for (int i = 0; i < n; i++) {nums[i] = tmp[i];}}
复杂度:
- 时间复杂度:O(n)
- 空间复杂度:O(n)
优点是直观易懂,但额外空间较大。
五、解法三:翻转法(推荐)
思路:
- 翻转整个数组;
- 翻转前 k 个元素;
- 翻转后 n-k 个元素。
代码实现:
public void rotateByReversals(int[] nums, int k) {int n = nums.length;// 1. 翻转整个数组reverse(nums, 0, n - 1);// 2. 翻转前 k 个元素(现在是原数组的最后 k 个)reverse(nums, 0, k - 1);// 3. 翻转剩余 n-k 个元素reverse(nums, k, n - 1);}private void reverse(int[] arr, int left, int right) {while (left < right) {int tmp = arr[left];arr[left++] = arr[right];arr[right--] = tmp;}}
复杂度:
- 时间复杂度:O(n)
- 空间复杂度:O(1)
这是最常用的解法,也是面试中推荐优先写出的方案。
六、解法四:环状替换法
思路:
从下标 0 开始,把元素放到它应该在的位置,再继续替换下一位置的元素,直到形成环,之后从下一个未处理的元素开始新的环,直到所有元素移动完成。
代码实现:
public void rotateByCyclicReplacements(int[] nums, int k) {int n = nums.length;int countMoved = 0; // 已经移动的元素个数for (int start = 0; countMoved < n; start++) {int current = start;int prevValue = nums[start];do {int nextIndex = (current + k) % n;int temp = nums[nextIndex];nums[nextIndex] = prevValue;prevValue = temp;current = nextIndex;countMoved++;} while (start != current);// 开始下一个循环组}}
复杂度:
- 时间复杂度:O(n)
- 空间复杂度:O(1)
实现略复杂,但更贴近数学规律。
七、代码汇总
完整可运行类:
public class Solution {/*** 主入口:默认使用原地翻转法(O(n) 时间,O(1) 空间)。* 如果你想测试其他方法,可以把下面一行替换成调用其它实现:* rotateWithExtraArray(nums, k);* rotateBruteForce(nums, k);* rotateByCyclicReplacements(nums, k);*/public void rotate(int[] nums, int k) {if (nums == null || nums.length <= 1) return;int n = nums.length;k = k % n;if (k == 0) return;// 默认:翻转法(推荐)rotateByReversals(nums, k);}/* ===========================方法1:暴力法(逐步右移)时间 O(n * k),空间 O(1)仅做学习示例,不推荐在大数据上使用=========================== */public void rotateBruteForce(int[] nums, int k) {int n = nums.length;for (int step = 0; step < k; step++) {// 每一步将最后一个元素移动到最前面,其余右移一位int last = nums[n - 1];for (int i = n - 1; i > 0; i--) {nums[i] = nums[i - 1];}nums[0] = last;}}/* ===========================方法2:使用额外数组(临时数组)时间 O(n),空间 O(n)简洁直观=========================== */public void rotateWithExtraArray(int[] nums, int k) {int n = nums.length;int[] tmp = new int[n];// 将 nums 的最后 k 个放在最前面,其他顺序跟随int start = n - k; // 从 nums[start] 开始复制到 tmp[0]for (int i = 0; i < n; i++) {tmp[i] = nums[(start + i) % n];}// 拷贝回原数组for (int i = 0; i < n; i++) {nums[i] = tmp[i];}}/* ===========================方法3:翻转法(三次反转)时间 O(n),空间 O(1)推荐:简单、原地、稳定思路:整体翻转 -> 翻转前 k 个 -> 翻转剩余 n-k 个=========================== */public void rotateByReversals(int[] nums, int k) {int n = nums.length;// 1. 翻转整个数组reverse(nums, 0, n - 1);// 2. 翻转前 k 个元素(现在是原数组的最后 k 个)reverse(nums, 0, k - 1);// 3. 翻转剩余 n-k 个元素reverse(nums, k, n - 1);}private void reverse(int[] arr, int left, int right) {while (left < right) {int tmp = arr[left];arr[left++] = arr[right];arr[right--] = tmp;}}/* ===========================方法4:环状替换(基于 GCD,原地)时间 O(n),空间 O(1)通过计算循环节来逐组替换元素=========================== */public void rotateByCyclicReplacements(int[] nums, int k) {int n = nums.length;int countMoved = 0; // 已经移动的元素个数for (int start = 0; countMoved < n; start++) {int current = start;int prevValue = nums[start];do {int nextIndex = (current + k) % n;int temp = nums[nextIndex];nums[nextIndex] = prevValue;prevValue = temp;current = nextIndex;countMoved++;} while (start != current);// 开始下一个循环组}}
}
八、总结
- 方法一:暴力法,直观但效率低。
- 方法二:额外数组法,代码简洁但需 O(n) 额外空间。
- 方法三:翻转法,时间 O(n)、空间 O(1),推荐作为默认解法。
- 方法四:环状替换法,数学性更强,适合展示思维深度。
面试建议:优先写翻转法,若时间允许可补充环状替换以展示全面性。