如果你在力扣上刷到第75题“颜色分类”,第一反应大概率是“这不就是排序吗”——但等你真去提交,就会发现事情没那么简单。这道题不能用库函数、要求原地修改、还希望你用一趟扫描加常数空间解决,本质上考的是快速排序里的分区思想(partition),更准确地说,是三路快排的“荷兰国旗”写法。而紧接着的第912题“排序数组”,直接逼着你手写一遍完整可用的快速排序,不能调 sort(),不能偷懒。这两道题放在一起刷,正好能把分治思想从“看懂”变成“会用”。
这篇文章就是围绕这两道题写的实战拆解。适合正在刷力扣热题100、准备算法面试,或者学过快排但从来没自己手写过一遍完整实现的人。我会把荷兰国旗分区的执行过程逐行讲透,把快排的递归结构拆开看,再聊复杂度、边界条件和刷题顺序。读完你不仅能过这两道题,拿到215、347、148这些变体题时,也会知道从哪里下手。
1. 颜色分类的隐藏考点:从荷兰国旗到三指针分区
1.1 题目背后的三类解法对比
题目给了你一个只包含 0、1、2 的数组,分别代表红色、白色、蓝色,要求原地排序,让所有 0 在前、1 居中、2 在后。不能调库函数,进阶要求是“仅使用常数空间的一趟扫描算法”。
我第一次见到这题时,脑子里冒出来的是计数排序:扫一遍统计 0、1、2 的出现次数,再按次数把数组重写一遍。这个做法的时间复杂度是 O(n),空间复杂度是 O(1),完全是合法解。
但为什么面试官通常不满意这个答案?因为“扫描两遍”这件事,在工程里意味着你必须遍历两次数组。第一遍统计,第二遍回填。而一趟扫描的方案,只需要遍历一次,在遍历过程中通过交换把每个元素放到该去的位置。这听起来只是常数级的时间差异,但在题目设计者眼里,这代表你有没有真正理解“分区”这个动作,而不是仅仅会用排序。
| 解法 | 时间复杂度 | 空间复杂度 | 遍历次数 | 是否原地 |
|---|---|---|---|---|
| 计数排序 | O(n) | O(1) | 两趟 | 是 |
| 普通双指针 | O(n) | O(1) | 一趟 | 是 |
| 三指针(荷兰国旗) | O(n) | O(1) | 一趟 | 是 |
普通双指针也可以一趟完成,但需要先确定一个“基准值”的边界,比如先把 0 归位,再处理 1 和 2。这要两轮循环。三指针方案才是真正的一遍搞定,也是快速排序三向切分的雏形。
这题的官方标签是“数组、双指针、排序”,但很多人忽略了一个关键信息:它的本质,就是快排 partition 的一个特例——选定一个基准 1,把小于基准的放左边,等于基准的放中间,大于基准的放右边。你一旦看懂这个,后面做912题时,三路快排的代码就是照着这个思路扩展出来的。
1.2 三指针代码的逐行拆解
先直接给出我最终提交的版本:
python复制class Solution:
def sortColors(self, nums: List[int]) -> None:
n = len(nums)
p0 = 0
p2 = n - 1
i = 0
while i <= p2:
if nums[i] == 0:
nums[i], nums[p0] = nums[p0], nums[i]
p0 += 1
i += 1
elif nums[i] == 1:
i += 1
else: # nums[i] == 2
nums[i], nums[p2] = nums[p2], nums[i]
p2 -= 1
这里有三个指针:
p0指向下一个0应该放置的位置,初始为0。p2指向下一个2应该放置的位置,初始为n-1。i是当前扫描指针,从0往右走。
核心逻辑可以概括成一句话:i 每遇到一个数,就看它是 0、1 还是 2。是 0 就扔到左边 p0 的位置,是 2 就扔到右边 p2 的位置,是 1 就什么都不做,继续往前走。
为什么遇到 0 交换后 i 可以放心加一?因为从左边换过来的元素,要么是 0 要么是 1。p0 始终在 i 的左侧或等于 i 的位置,它指向的区域已经被扫描过了,所以换过来的元素不可能大于 1,不需要再检查。
为什么遇到 2 交换后 i 不能直接加一?因为从 p2 换过来的元素我们没有见过,它可能是 0,也可能是 1,还可能是 2。如果直接跳过,就会漏处理。所以 while 循环要在这里继续检查 nums[i] 的新值。
1.3 双指针解法的一个补充视角
除了三指针,还有一种思路是先把 0 归位,再把 1 归位。代码如下:
python复制class Solution:
def sortColors(self, nums: List[int]) -> None:
n = len(nums)
p = 0
for i in range(n):
if nums[i] == 0:
nums[i], nums[p] = nums[p], nums[i]
p += 1
for i in range(p, n):
if num
