一、 题目与代码重现
题目描述
-
题目名称:189. 轮转数组
-
难度:中等
-
题目要求:给定一个整数数组
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:
-
限制条件:
1 <= nums.length <= 10^5-2^31 <= nums[i] <= 2^31 - 10 <= k <= 10^5
原始代码(JavaScript)
javascript
var rotate = function(nums, k) {
const n = nums.length;
k = k % n;
const temp = new Array(n);
// 将元素放到新位置
for (let i = 0; i < n; i++) {
temp[(i + k) % n] = nums[i];
}
// 复制回原数组
for (let i = 0; i < n; i++) {
nums[i] = temp[i];
}
};
二、 标准解题逻辑步骤
核心思想:辅助数组映射法
向右轮转 k 位,本质上是将原数组 nums 的元素重新排列:
原本在位置 i 的元素,轮转后应移动到 (i + k) % n 的位置(因为向右移动)。
详细流程:
-
归一化
k:
由于轮转n次会回到原数组,因此k对n取模,k = k % n,减少不必要的操作。 -
创建辅助数组
temp:
初始化一个与nums等长的空数组,用于暂存轮转后的结果。 -
映射填充:
遍历原数组的每个索引i:- 计算目标位置
target = (i + k) % n - 将
nums[i]赋值给temp[target]
- 计算目标位置
-
写回原数组:
将temp中的所有元素依次复制回nums,完成原地修改(虽然用了额外空间,但从函数外部看,nums已被修改)。
注意:虽然用了
O(n)额外空间,但题目要求的"原地"通常指不返回新数组,且数组内容在函数返回后改变。这种做法是大多数解法中最直观、最安全的方式。
三、 API 与语法深度解析
| 语法 / API | 作用 | 参数说明 | 在该题中的场景 |
|---|---|---|---|
new Array(n) | 创建一个长度为 n 的空数组 | n:数组长度 | 创建临时数组存储轮转结果 |
k = k % n | 取模运算,得到实际轮转步数 | 左侧 k 重新赋值为余数 | 当 k >= n 时,减少冗余轮转 |
(i + k) % n | 计算轮转后的新索引 | i:原索引,k:步数 | 确保索引始终在 [0, n-1] 范围内 |
nums[i] = temp[i] | 数组元素赋值 | 右侧值赋给左侧数组元素 | 将临时数组内容复制回原数组 |
注意:
%在 JavaScript 中对于负数也返回非负余数(与被除数同号),但这里i和k均为非负,所以结果正确。
四、 得分重点与理解难点划分
得分重点
- 正确处理
k > nums.length的情况(取模)。 - 能够实现元素位置映射的推导公式
(i + k) % n。 - 保证修改原数组(无论是否使用辅助空间)。
理解难点
- 轮转方向:向右轮转
k位 ≠ 向右移动每个元素k格(而是整体循环移动)。 - 索引映射的取模运算:初学者容易写成
(i - k) % n或忘记取模。 - 为什么需要
k = k % n:如果k = n,数组等于没变,但执行轮转会浪费性能。
五、 极易踩错的细节与边界错误
边界条件
- 数组长度为 1:
n = 1,任何k取模后都是 0,数组不变。但如果代码中没有处理k % n(例如k = 100000),可能导致数组越界或无限循环。 k = 0:k % n = 0,(i + 0) % n = i,数组保持原样,复制回nums后结果不变。k大于等于n:取模后变成较小值,避免无效移动。
常见逻辑陷阱
- 直接使用
nums作为源同时修改:如果尝试原地交换会弄乱数据,所以必须借助辅助数组或三次反转法。 - 忘记
return或返回新数组:题目要求修改原数组,但函数不要求返回值(void)。如果返回了temp,用例会判断nums未改变而报错。
超时/内存风险
nums.length最大10^5,本解法O(n)时间和空间,完全可接受。- 如果用
splice和push反复操作,会因O(n^2)而超时。
六、 复杂度对比与优化方案
基础写法对比
-
暴力法(每次右移 1 位,重复
k次):- 时间复杂度:
O(n * k),当k接近n时退化到O(n^2) - 空间复杂度:
O(1) - ❌ 在限制条件下(
n=10^5,k=10^5)会严重超时。
- 时间复杂度:
-
辅助数组法(本题代码) :
- 时间复杂度:
O(n) - 空间复杂度:
O(n) - ✅ 直观且安全,面试中容易快速写出。
- 时间复杂度:
最优解:三次反转法(原地,O(1) 空间)
核心思路:
- 反转整个数组
- 反转前
k个元素 - 反转后
n-k个元素
原理:
原数组 [1,2,3,4,5,6,7], k=3
- 整体反转 →
[7,6,5,4,3,2,1] - 反转前 3 个 →
[5,6,7,4,3,2,1] - 反转后 4 个 →
[5,6,7,1,2,3,4]✅
优化后代码(JavaScript)
javascript
var rotate = function(nums, k) {
const n = nums.length;
k = k % n;
if (k === 0) return; // 可提前结束
// 辅助反转函数
function reverse(left, right) {
while (left < right) {
[nums[left], nums[right]] = [nums[right], nums[left]];
left++;
right--;
}
}
reverse(0, n - 1);
reverse(0, k - 1);
reverse(k, n - 1);
};
复杂度:
- 时间:
O(n)(三次遍历,每次反转部分元素) - 空间:
O(1)(仅用常数变量)
对比结论:若面试要求原地修改且空间越少越好,三次反转法是最佳选择。若允许
O(n)空间,辅助数组法更易理解且不易出错,通常也能通过力扣的判定。