本文将带你彻底搞懂 LeetCode 第 189 题《Rotate Array》——如何将一个数组向右旋转
k个位置。我们将从最直观的“新开数组”开始,一步步推导出最优解“三次反转法”,并在过程中帮你理解:为什么只需三次翻转,就能完成复杂的元素重排?
🔍 问题描述
给定一个整数数组 nums 和一个非负整数 k,请将数组向右旋转 k 个位置。
示例:
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
💡 注意:LeetCode 要求的是 原地修改(in-place),即不能创建新数组,只能直接修改原数组。
✅ 解法一:开辟新数组(直观易懂)
这是大多数初学者的第一反应:先算出每个元素的新位置,再放到新数组里。
🧠 思路
对于原数组中的第 i 个元素,它在旋转后应该出现在位置 (i + k) % n。
所以我们可以:
- 创建一个新数组
newArr - 遍历原数组,把每个元素放到它“该去的位置”
- 最后再把
newArr的内容拷贝回原数组
💻 代码实现
var rotate = function(nums, k) {
const n = nums.length;
k = k % n; // 处理 k > n 的情况
const newArr = new Array(n);
for (let i = 0; i < n; i++) {
newArr[(i + k) % n] = nums[i];
}
// 拷贝回原数组
for (let i = 0; i < n; i++) {
nums[i] = newArr[i];
}
};
⚖️ 复杂度分析
- 时间复杂度:O(n)
- 空间复杂度:O(n)
✅ 优点:逻辑清晰,容易理解和实现
❌ 缺点:使用了额外空间,不符合“原地修改”的要求
❓ 那我们如何实现原地修改呢?
这是很多人的下一个疑问:能不能不新建数组,只用原数组本身完成旋转?
如果我们不能开新空间,就不能“先存再放”。那怎么办?
我们需要一种不依赖临时存储的方法,来重新排列元素。
这时候,就轮到一个经典算法登场了——三次反转法(Three Reversal Algorithm) 。
🚀 解法二:原地修改 —— 三次反转法(高效优雅)
这可能是我见过最巧妙的数组操作之一。
🌟 核心思想
右旋转 k 位,本质是:
- 把数组分成两部分:前
n−k个元素(A) 和 后k个元素(B) - 目标是变成:B + A
但问题是:我们不能直接搬动整块数据。
于是,我们换一种思路:先打乱顺序,再局部纠正。
🔁 三次反转的步骤
让我们一步一步来看:
① 整体反转
reverse(nums, 0, n - 1);
→ 把整个数组倒过来,变成 rev(B) + rev(A)
此时,B 的元素在前面,A 在后面,但各自内部顺序是反的
② 反转前 k 个
reverse(nums, 0, k - 1);
→ 把 rev(B) 变回 B,现在是 B + rev(A)
③ 反转后 n−k 个
reverse(nums, k, n - 1);
→ 把 rev(A) 变回 A,最终得到 B + A ✅
💡 举个例子
原数组:[1,2,3,4,5], k=2
| 步骤 | 数组状态 | 说明 |
|---|---|---|
| 初始 | [1,2,3,4,5] | A=[1,2,3], B=[4,5] |
| ① 全反转 | [5,4,3,2,1] | = rev(B) + rev(A) |
| ② 前2个反转 | [4,5,3,2,1] | = B + rev(A) |
| ③ 后3个反转 | [4,5,1,2,3] | = B + A ✅ |
完美!
💻 代码实现
var rotate = function(nums, k) {
const n = nums.length;
k = k % n;
// 三次反转
reverse(nums, 0, n - 1); // 整体反转
reverse(nums, 0, k - 1); // 前 k 个反转
reverse(nums, k, n - 1); // 后 n-k 个反转
};
function reverse(arr, start, end) {
while (start < end) {
[arr[start], arr[end]] = [arr[end], arr[start]];
start++;
end--;
}
}
⚖️ 复杂度分析
- 时间复杂度:O(n)
- 空间复杂度:O(1)
✅ 完美满足“原地修改”要求
✅ 优雅、高效,体现算法之美
🤔 为什么三次反转能行?
“因为右旋转后,后面 k 个数一定会跑到前面,而前面 n−k 个数一定会被移到后面。
所以第一次整体反转,先把这两部分‘放到大致正确的位置’,但顺序是反的;
然后再分别对前 k 个和后 n−k 个进行反转,把它们各自的顺序‘纠正回来’。”
✨ 这正是三次反转法的精髓!
- 第一次反转:位置对了,顺序反了
- 第二次和第三次反转:顺序正了,位置也对了
这种“先放对位置,再调正顺序”的思维,是解决这类问题的关键。
📌 总结对比
| 方法 | 是否原地修改 | 时间复杂度 | 空间复杂度 | 推荐场景 |
|---|---|---|---|---|
| 新数组法 | ❌(需拷贝) | O(n) | O(n) | 快速实现、教学演示 |
| 三次反转法 | ✅ | O(n) | O(1) | 面试、性能敏感场景 |
💡 面试建议:先写出新数组法展示思路,再优化为三次反转法,体现你的进阶能力。
🎁 小贴士
- 别忘了
k = k % n!否则当k > n时会出错。 - 如果允许返回新数组(如某些变种题),第一种方法完全够用。
- 三次反转法还可用于字符串旋转、环形缓冲区等问题。
✅ 结语
数组旋转看似简单,却藏着精妙的算法思想。从“新开数组”的直觉,到“三次反转”的优雅,我们不仅解决了问题,更理解了如何用简单操作组合出复杂效果。
希望这篇文章能帮你彻底掌握这道经典题!如果你也在面试中遇到过类似问题,欢迎评论区分享你的经历~