题目描述
思路解析动画文字版
最直接是计数排序:第一趟数出 0、1、2 各几个,第二趟按数量从头重填。结果对,但要扫两趟。题目只有三种值,其实一趟就能就地分好——这正是三指针的用武之地。
关键招:l 左边全是已就位的 0,r 右边全是已就位的 2,i 是当前检查位。i 遇 0 就和 l 换、l 和 i 一起前进;遇 2 就和 r 换、r 后退但 i 留在原地;遇 1 就 i 直接前进。i 越过 r 就排完——一趟、原地。
三指针就位:三个指针就位:l 和 i 都从最左边出发,r 在最右边。i 负责逐个检查,l 把 0 推到左段、r 把 2 推到右段。
i=0 看到 2 → 换到右边:当前是 2,和 r(下标 5)交换,r 退到 4。注意 i 不前进——换过来的是个 0,还没检查过,下一步要在原地重判它。
i=0 重判 · 看到 0 → 换到左边:重判 i=0:换过来的正是 0,和 l(下标 0)交换,其实是自己跟自己换。l 和 i 都前进到 1,下标 0 锁定为红色 0,变灰。
i=1 看到 0 → 换到左边:又是 0,和 l(下标 1)交换,l 和 i 都前进到 2。左边已是 [0, 0] 两个就位的红色。
i=2 看到 2 → 换到右边:当前是 2,和 r(下标 4)交换,r 退到 3。i 仍然不动,换来的 1 还要再判一次。
i=2 重判 · 看到 1 → 前进:重判 i=2:换过来是 1,本就该在中间,不交换,i 直接前进到 3。
i=3 看到 1 → 前进越过 r:又是 1,不交换,i 前进到 4。此时 i 越过了 r(4 大于 3),r 右边的 [2, 2] 早已就位,扫描结束。
完成:一趟扫描完成排序:l 左边全 0、r 右边全 2、中间全 1,得到 [0, 0, 1, 1, 2, 2]。
灵魂帧慢放 · 三段不变量:慢放看清四段:l 左边全是 0、i 左边到 l 之间全是 1、r 右边全是 2,只有 i 到 r 之间还没看。每动一步,这四段的边界都严格成立——这就是荷兰国旗的不变量。
l 守左段全 0、r 守右段全 2,中间 [l, i) 是已确定的 1、[i, r] 是待处理区——这就是分区思想,快排的 partition 也是同一招。
雷区实演 · 遇 2 后 i 也前进会怎样:用本例演一遍错法:i=0 遇 2,和 r 换后 [0,0,2,1,1,2]。如果 i 也跟着前进到 1,那个刚换来的 0 就被跳过,l 再也碰不到它,最后下标 0 卡着个本该归左的 0——结果排错。所以遇 2 后 i 必须原地。
边界三连 · 真数据实演:三个边界都不崩:空数组 r=-1,i 小于等于 r 直接为假、循环一次都不进。单元素 nums=[2],i=0、r=0 进一次换自己、r 退到 -1 就停。全相同 [1,1,1] 全走中间分支、i 扫到底即可。
面试追问:面试高频四连:为什么不用 sort、对比计数排序、为什么遇 0 和遇 2 不对称、能否推广到 k 类。
迁移阶梯:把这题练到能复述后再迁移:LC283 移动零是「两段分区」(非零 / 零),快排 partition 是「小于基准 / 大于基准」两段,本题是三段——都是同一套「指针守边界、扫描归位」的分区思想。去数组专题里把它们串起来,再用师兄答疑追问「为什么 i 有时不动」。
边界三连:三类极端输入都不崩:空数组、单元素、全相同。盯住循环条件 i 小于等于 r,它同时管住了空数组和单元素两个边界。
参考代码
class Solution: def sortColors(self, nums): l, i, r = 0, 0, len(nums) - 1 while i <= r: if nums[i] == 0: nums[l], nums[i] = nums[i], nums[l] l += 1 i += 1 elif nums[i] == 2: nums[r], nums[i] = nums[i], nums[r] r -= 1 # i 不动! else: i += 1复杂度
- 时间复杂度:O(n),每轮要么 i 前进、要么 r 后退,i 和 r 相向而行最多走 n 步
- 空间复杂度:O(1),只用 l、i、r 三个下标,原地交换、不开计数数组
可套用的代码模板
记住三条判据,不背具体代码:i 越过 r 即停;和已扫过的左段换 → i 跟进;和没扫过的右段换 → i 留原地。把「该放左 / 该放右」替换成本题条件,移动零、快排 partition 都能套。
# 三指针分区骨架:l 守左段、r 守右段、i 扫描l, i, r = 0, 0, n - 1while i <= r: # i 越过 r 即停 if 该放左段(nums[i]): # 例: nums[i]==0 swap(l, i); l += 1; i += 1 # 左段已扫过 → i 跟进 elif 该放右段(nums[i]): # 例: nums[i]==2 swap(r, i); r -= 1 # 右段没扫过 → i 不动! else: i += 1 # 留中段,直接走易错点
面试追问把动画讲成自己的话
追问为什么不用 sort,非要三指针?
追问和计数排序比有什么优劣?
追问遇 0 和 l 换、遇 2 和 r 换,为什么处理方式不对称?
追问如果值不止三种、要分成 k 段呢?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
最接近的三数之和
LeetCode 16 · 中等 · 沿着 双指针 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题