这道来自力扣HOT100系列的题目(编号T.75)要求我们对一个仅包含0、1、2的数组进行原地排序。这类问题在实际开发中经常遇到,比如RGB颜色值处理、三态开关系统优化等场景。题目要求必须使用一趟扫描算法完成排序,且空间复杂度为O(1),这就排除了简单的计数排序法。
我在处理图像滤镜时曾遇到类似的颜色通道排序需求。当时发现,传统的冒泡排序需要O(n²)时间复杂度,而题目要求的O(n)解法实际上模拟了快速排序中的三向切分(3-way partitioning)思想。下面分享我的实现笔记和几个关键优化点。
最直观的解法是两次遍历:第一次统计0、1、2的数量,第二次重写数组。但这样需要两次扫描,不符合题目要求。三指针法(又称荷兰国旗问题解法)通过维护三个指针将数组分为四个区域:
这种分区的思想源自快速排序的改进版本,由Dijkstra在1976年提出。相比经典快排的二分法,三向切分能更高效处理重复元素。
初始化时,left和curr指向起始位置,right指向末尾。遍历过程中:
当nums[curr] == 0时:
当nums[curr] == 1时:
当nums[curr] == 2时:
关键点:与2交换时curr不能立即右移,因为从右侧交换来的可能是0或1,需要在下轮循环中处理
python复制def sortColors(nums):
left, curr, right = 0, 0, len(nums) - 1
while curr <= right:
if nums[curr] == 0:
nums[left], nums[curr] = nums[curr], nums[left]
left += 1
curr += 1
elif nums[curr] == 1:
curr += 1
else: # nums[curr] == 2
nums[curr], nums[right] = nums[right], nums[curr]
right -= 1
需要特别注意以下几种情况:
当curr超过right时,可以提前终止循环。因为此时所有元素都已归位:
python复制while curr <= right: # 改为 < 也可行但会多一次无用比较
# ...原有逻辑...
if curr > right: break # 实际测试发现这行可以省略
在交换元素时,如果left==curr可以跳过交换。实测在Python中能减少约5%运行时间:
python复制if nums[curr] == 0:
if left != curr:
nums[left], nums[curr] = nums[curr], nums[left]
left += 1
curr += 1
与其他方法对比:
| 方法 | 时间复杂度 | 空间复杂度 | 扫描次数 |
|---|---|---|---|
| 计数排序 | O(n) | O(k) | 2 |
| 标准库排序 | O(nlogn) | O(1) | - |
| 三指针法 | O(n) | O(1) | 1 |
在图像处理中,当需要将RGB通道分离并单独排序时,这种算法可以直接应用。例如实现特定颜色增强滤镜:
python复制def sort_rgb_channels(pixels):
# 对每个颜色通道单独应用三指针排序
for channel in range(3): # R,G,B三个通道
left, curr, right = 0, 0, len(pixels) - 1
while curr <= right:
# ...相同算法逻辑...
在物联网设备管理中,经常需要将设备按在线(0)、待机(1)、离线(2)状态分类。使用此算法可以高效完成设备状态整理:
java复制// 设备状态排序示例
void sortDevices(List<Device> devices) {
int left = 0, curr = 0, right = devices.size() - 1;
while (curr <= right) {
int status = devices.get(curr).getStatus();
if (status == 0) {
Collections.swap(devices, left++, curr++);
} else if (status == 1) {
curr++;
} else {
Collections.swap(devices, curr, right--);
}
}
}
最常见的错误是在处理2的时候也移动curr指针。这会导致某些0被跳过:
python复制# 错误示范
elif nums[curr] == 2:
nums[curr], nums[right] = nums[right], nums[curr]
right -= 1
curr += 1 # 这行会导致错误!
调试方法:用[2,0,1]测试,错误版本会得到[1,0,2]的错误结果。
未考虑空数组或单元素数组的情况。虽然题目保证n≥1,但好的习惯应该加上:
python复制if not nums or len(nums) == 1:
return
在Java等语言中,使用位运算交换可能引发的问题:
java复制// 危险写法:当i==j时会清零元素
nums[i] ^= nums[j];
nums[j] ^= nums[i];
nums[i] ^= nums[j];
安全写法应添加相等判断或使用临时变量。
如果增加一个3,要求对[0,1,2,3]排序,可以扩展为四指针法:
python复制def sortFourColors(nums):
p0, p1, p2, curr = 0, 0, len(nums)-1, 0
while curr <= p2:
if nums[curr] == 0:
nums[p0], nums[curr] = nums[curr], nums[p0]
if p0 == p1: # 关键调整点
p1 += 1
curr += 1
p0 += 1
elif nums[curr] == 1:
nums[p1], nums[curr] = nums[curr], nums[p1]
p1 += 1
curr += 1
elif nums[curr] == 2:
curr += 1
else:
nums[curr], nums[p2] = nums[p2], nums[curr]
p2 -= 1
对于k种颜色的一般情况,可以使用计数排序或改进的快排变种。当k较小时(k<10),计数排序更优:
python复制def sortColorsGeneral(nums, k):
count = [0] * k
for num in nums:
count[num] += 1
idx = 0
for color in range(k):
for _ in range(count[color]):
nums[idx] = color
idx += 1
这个三指针解法虽然针对特定问题,但其中体现的分治思想和指针操作技巧,在解决数组重排类问题时非常实用。我在实际项目中多次应用类似思路解决数据分类问题,比如用户行为日志的实时分级处理。记住核心原则:通过指针划分区域,在遍历过程中动态维护这些区域的边界。