轮转数组 图解题解
整体翻、前段翻、后段翻——三次反转原地轮转数组,O(n) 时间 O(1) 空间。
想把一条绳子右移 k 格,只需三次翻转:先把整条绳子翻过来,再把前 k 段翻回来,最后把剩余段翻回来——三刀还原了正确顺序,不用逐格搬动,O(n) 时间、原地完成。
这道题到底在问什么
- 输入
- nums=[1,2,3,4,5,6,7],k=3
- 输出
- [5,6,7,1,2,3,4]
最优解:一步一步想明白
- 3每转一步,后 n-1 个元素全体后移一位,k 次共做 n×k 次操作。k 接近 n 时直接退化成 O(n²)。得换思路。
- 4朴素法:准备把末位 7 挪到头部朴素法:每次把末尾元素插入头部,其他 n-1 个元素全体右移一位。
- 5挪完 1 次:7 到了头部,还需再挪 2 次才能完成 k=3挪 1 次只完成 1 格轮转,k=3 就要挪 3 次,每次移动 n 个元素,总代价 O(n·k),k 接近 n 时退化成 O(n²)。必须换思路。
- 6右转 k 步等于把后 k 个挪到前面。先整体反转,再分两段反转,三刀就还原了真正想要的顺序——O(n) 时间、O(1) 空间。
- 7轮转目标:把 B 段整体挪到 A 段前面k=3,所以后 k=3 个元素组成 B 段(高亮),前 n-k=4 个是 A 段(暗色)。轮转的本质就是把 B 搬到 A 前面——但怎么原地搬?靠三次反转。
- 8k=3,n=7,k%n=3(不变)第一件事:k 对 n 取模。k=10、n=7 时其实只转 3 步;不取模会白做功。
- 9l=0, r=6 → 对撞交换第一次反转开始:l 从最左、r 从最右向中间靠拢,两两交换。
- 10[7,2,3,4,5,6,1] · l=1, r=51 和 7 交换完毕,l 右移、r 左移,继续向中间夹。
- 11[7,6,3,4,5,2,1] · l=2, r=42 和 6 交换。
- 12[7,6,5,4,3,2,1] · 整体反转完成3 和 5 交换,l 和 r 相遇,整体反转完成:[7,6,5,4,3,2,1]。
- 13整体反转后:B'=[7,6,5] 在前,A'=[4,3,2,1] 在后整体反转做了两件事:① A 和 B 的相对位置互换(B' 跑到前面了);② A、B 各自内部顺序颠倒。接下来对 B' 单独反转还原 B,对 A' 单独反转还原 A,最终正是 [B|A] = 轮转结果。
- 14整体反转结果 [7,6,5,4,3,2,1]:B'(索引0-2)倒序,A'(索引3-6)倒序高亮=B'=[7,6,5](目标是5,6,7,倒序了);暗色=A'=[4,3,2,1](目标是1,2,3,4,也倒序了)。各自再反转一次就回正序,三刀结束。
- 15[7,6,5,…] 反转前3个,l=0, r=2第二次反转只动前 k=3 个。把 reverse(B')=[7,6,5] 变回 B=[5,6,7]。
- 16即将交换 nums[0] 与 nums[2](7↔5),l=0, r=2l=0、r=2 对撞:高亮的 7 和 5 互换。中间的 6 不参与此次交换。交换后 l 和 r 都指向 1,循环结束。
- 17[5,6,7,4,3,2,1] · 前 k 段归位前三个反转好了:5、6、7 回到正序,它们就是轮转后的前三个。
- 18[…,4,3,2,1] 反转后4个,l=3, r=6第三次反转只动后 n-k=4 个。把 reverse(A')=[4,3,2,1] 变回 A=[1,2,3,4]。
- 19[5,6,7,1,3,2,4] · l=4, r=54 和 1 交换完毕,继续夹。
- 20[5,6,7,1,2,3,4] ✓3 和 2 交换,后四个变成 [1,2,3,4],最终结果 [5,6,7,1,2,3,4]。三次反转搞定!
- 23把这句话记住:整体反转相当于把 A 和 B 的顺序颠倒同时把各自内部也颠倒,再各自修正一遍,就还原出 B+A。
- 26k=0(或 k=n 取模后为 0):三步全为空操作k=0 时三步的净效果是两次整体反转互相抵消,数组完全不变。k=n 若不取模,rev(0, k-1)=rev(0, n-1) 变成整体反转而非前段反转,结果全错。第一行 k%=n 是整个算法的安全阀。
- 31最终结果 [5,6,7,1,2,3,4]:三刀完成轮转三刀全部落完:[1,2,3,4,5,6,7] → 整体反转 → [7,6,5,4,3,2,1] → 反转前3 → [5,6,7,4,3,2,1] → 反转后4 → [5,6,7,1,2,3,4]。
- 32掌握三次反转后,字符串的循环移位(LC796 旋转字符串)、原地旋转矩阵(LC48)都能用「反转分段」思路迁移。进入数组专题继续刷。
⚠️ 容易写错的地方
✗ 错:忘写 k %= n
✓ 对:第一行就做 k %= n
k=14, n=7 时其实一步都不用转;不取模会多做无效交换,且 rev(0, k-1) 的 k-1 可能越界
✗ 错:三次反转顺序写错:先反转前 k,再整体反转
✓ 对:顺序固定:整体 → 前k → 后n-k
整体反转必须最先做,才能把 A 和 B 的相对位置颠倒;后两步的前提是整体已倒置
✗ 错:rev 区间写成 [0, k] 或 [k+1, n-1](差一)
✓ 对:rev(0, k-1) 和 rev(k, n-1),闭区间,分界点在 k-1 和 k
差一错会让第 k 个元素既被第二步反转、又被第三步反转,最终位置出错
完整代码(Python / C++ / Java)
Python
class Solution:
def rotate(self, nums, k):
n = len(nums)
k %= n
def rev(l, r):
while l < r:
nums[l], nums[r] = nums[r], nums[l]
l += 1; r -= 1
rev(0, n - 1) # 整体反转
rev(0, k - 1) # 反转前 k 个
rev(k, n - 1) # 反转后 n-k 个C++
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int n = (int)nums.size();
k %= n;
reverse(nums.begin(), nums.end()); // 整体反转
reverse(nums.begin(), nums.begin() + k); // 反转前 k 个
reverse(nums.begin() + k, nums.end()); // 反转后 n-k 个
}
};Java
class Solution {
public void rotate(int[] nums, int k) {
int n = nums.length;
k %= n;
rev(nums, 0, n - 1); // 整体反转
rev(nums, 0, k - 1); // 反转前 k 个
rev(nums, k, n - 1); // 反转后 n-k 个
}
private void rev(int[] a, int l, int r) {
while (l < r) {
int tmp = a[l]; a[l] = a[r]; a[r] = tmp;
l++; r--;
}
}
}复杂度
时间复杂度
O(n)
三次反转合计每个元素最多被交换 2 次,总操作数不超过 2n
空间复杂度
O(1)
只用 l/r 两个指针和一个 tmp,不开额外数组
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 轮转数组 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么三次反转能实现轮转?直觉上怎么解释?+
把数组看成 [A | B],轮转就是 [B | A]。整体反转得 [B' | A'](A、B 各自内部顺序也颠倒了);再对 B' 单独反转还原为 B,对 A' 单独反转还原为 A,最终就是 [B | A]。
O(1) 空间的三次反转 vs 开额外数组法,各有什么优劣?+
额外数组法新开长度 n 的数组记录每个元素的新位置,空间 O(n);三次反转法 O(1) 更省,但需要理解原地交换语义。面试首选三次反转,体现对原地算法的掌握。
k 为什么要先做 k%=n,直接用大 k 会出什么问题?+
k 可能远大于 n(题目约束 k 可达 2×10⁵),不取模则 rev(0, k-1) 的 k-1 越界;即便不越界,转 n 圈等于没转,做了大量无效交换。取模把 k 压到 [0, n-1]。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。