📌 题目链接:75. 颜色分类 - 力扣(LeetCode)
🔍 难度:中等 | 🏷️ 标签:数组、双指针、原地操作、荷兰国旗问题
⏱️ 目标时间复杂度:O(n)
💾 空间复杂度:O(1)
🧠 题目分析
给定一个包含 0(红)、1(白)、2(蓝) 的整数数组 nums,要求原地排序,使得所有 0 在前,1 居中,2 在后。
⚠️ 不能使用库函数如 sort(),且需满足 一趟扫描 + 常数空间 的进阶要求。
这道题是经典的 「荷兰国旗问题」(Dutch National Flag Problem),由图灵奖得主 Edsger W. Dijkstra 提出,用于演示如何用三路划分(Three-way Partitioning)高效处理含三种取值的数组。
在面试中,此题常被用来考察:
- 对 双指针 / 三指针 技巧的掌握;
- 对 原地交换 和 边界控制 的理解;
- 是否能从暴力解法 → 优化到最优解。
🔑 核心算法及代码讲解
本题有 三种主流解法,我们重点讲解 方法三:双指针(p0 与 p2),因其最符合“一趟扫描 + O(1) 空间”的进阶要求,也是面试官最期待的答案。
✅ 方法三:双指针(左指针 p0,右指针 p2)
🎯 核心思想
- 使用两个指针:
p0:指向 下一个 0 应该放置的位置(从左往右);p2:指向 下一个 2 应该放置的位置(从右往左)。
- 遍历指针
i从左向右移动,但 当i > p2时停止(因为p2右侧已全是 2,无需再处理)。 - 关键细节:遇到 2 时,不能直接
i++,因为从p2交换过来的元素可能是 0 或 1,需再次检查!
📜 C++ 代码(带详细行注释)
class Solution {
public:
void sortColors(vector<int>& nums) {
int n = nums.size();
int p0 = 0; // 指向下一个 0 应放的位置(左边界)
int p2 = n - 1; // 指向下一个 2 应放的位置(右边界)
// 注意:i <= p2!因为 p2 右侧已全是 2,无需再处理
for (int i = 0; i <= p2; ++i) {
// 当前元素是 2:不断与 p2 交换,直到 nums[i] != 2
while (i <= p2 && nums[i] == 2) {
swap(nums[i], nums[p2]);
--p2; // p2 左移,缩小未处理区域
// 注意:此处不 i++!因为交换来的 nums[i] 可能是 0/1,需继续处理
}
// 此时 nums[i] 只可能是 0 或 1
if (nums[i] == 0) {
swap(nums[i], nums[p0]);
++p0; // p0 右移
}
// 如果是 1,直接跳过(留在中间区域)
}
}
};
💡 为什么需要 while 循环处理 2?
假设 nums = [2, 0, 2, 1, 1, 0],初始 p2 = 5。
- 第一次
i=0,nums[0]=2,与nums[5]=0交换 →[0, 0, 2, 1, 1, 2],p2=4。 - 此时
nums[0]=0,若直接i++,会错过这个 0! - 但我们的代码在交换后 不立即
i++,而是留在原地检查新值(0),再进入if (nums[i]==0)分支处理。
这就是 while 循环的关键作用:确保从右侧交换过来的元素也被正确归位。
🧩 解题思路(分步拆解)
-
初始化指针:
p0 = 0:0 区域的右边界(exclusive);p2 = n-1:2 区域的左边界(exclusive);i = 0:当前遍历位置。
-
遍历数组(
i <= p2):- 情况一:
nums[i] == 2- 与
nums[p2]交换; p2--;- 不移动
i,继续检查新nums[i](可能为 0/1/2)。
- 与
- 情况二:
nums[i] == 0- 与
nums[p0]交换; p0++;i++(因为从左侧交换来的只能是 1 或已处理过的 0,安全)。
- 与
- 情况三:
nums[i] == 1- 直接
i++(1 应留在中间,无需移动)。
- 直接
- 情况一:
-
终止条件:当
i > p2,说明中间区域已全部为 1,左右分别为 0 和 2,排序完成。
📊 算法分析
| 方法 | 时间复杂度 | 空间复杂度 | 扫描次数 | 面试推荐度 |
|---|---|---|---|---|
| 计数重写 | O(n) | O(1) | 2 次 | ⭐⭐ |
| 双指针(p0/p1) | O(n) | O(1) | 1 次 | ⭐⭐⭐ |
| 双指针(p0/p2)✅ | O(n) | O(1) | 1 次 | ⭐⭐⭐⭐⭐ |
- 时间复杂度:每个元素最多被访问两次(一次由
i,一次由p2交换),仍为 O(n)。 - 空间复杂度:仅用几个指针变量,O(1)。
- 稳定性:本题不要求稳定排序(相同颜色无区别),故交换合法。
💬 面试加分点:
- 能说出这是 Dijkstra 提出的荷兰国旗问题;
- 能解释 为何处理 2 时要用
while而不是if;- 能对比三种方法的优劣,并指出方法三是最优解。
💻 代码
✅ C++ 完整代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
class Solution {
public:
void sortColors(vector<int>& nums) {
int n = nums.size();
int p0 = 0, p2 = n - 1;
for (int i = 0; i <= p2; ++i) {
while (i <= p2 && nums[i] == 2) {
swap(nums[i], nums[p2]);
--p2;
}
if (nums[i] == 0) {
swap(nums[i], nums[p0]);
++p0;
}
}
}
};
// 测试
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
Solution sol;
vector<int> nums1 = {2,0,2,1,1,0};
sol.sortColors(nums1);
for (int x : nums1) cout << x << " "; // 输出: 0 0 1 1 2 2
cout << "\n";
vector<int> nums2 = {2,0,1};
sol.sortColors(nums2);
for (int x : nums2) cout << x << " "; // 输出: 0 1 2
cout << "\n";
return 0;
}
✅ JavaScript 完整代码
/**
* @param {number[]} nums
* @return {void} Do not return anything, modify nums in-place instead.
*/
var sortColors = function(nums) {
const n = nums.length;
let p0 = 0; // next position for 0
let p2 = n - 1; // next position for 2
for (let i = 0; i <= p2; i++) {
// Keep swapping 2 to the end until nums[i] is not 2
while (i <= p2 && nums[i] === 2) {
[nums[i], nums[p2]] = [nums[p2], nums[i]]; // ES6 swap
p2--;
}
if (nums[i] === 0) {
[nums[i], nums[p0]] = [nums[p0], nums[i]];
p0++;
}
}
};
// Test
console.log(sortColors([2,0,2,1,1,0])); // [0,0,1,1,2,2]
console.log(sortColors([2,0,1])); // [0,1,2]
🌟 本期完结,下期见!🔥
👉 点赞收藏加关注,新文更新不迷路。关注专栏【算法】LeetCode Hot100刷题日记,持续为你拆解每一道热题的底层逻辑与面试技巧!
💬 欢迎留言交流你的解法或疑问!一起进步,冲向 Offer!💪
📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!