【LeetCode 热题 100】75. 颜色分类——双指针
Problem: 75. 颜色分类
文章目录
- 整体思路
- 完整代码
- 时空复杂度
- 时间复杂度:O(N)
- 空间复杂度:O(1)
整体思路
这段代码旨在解决经典的 “颜色分类” (Sort Colors) 问题,也常被称为 “荷兰国旗问题” (Dutch National Flag problem)。问题要求对一个包含 0(红色)、1(白色)、2(蓝色)三种元素的数组进行原地排序,使得所有相同的元素相邻,并按 0、1、2 的顺序排列。
该算法采用了一种非常精妙的 单次遍历、双指针 的原地排序策略。它通过一次遍历,同时维护 0 的区域和 1 的区域的边界,巧妙地将遇到的 0 和 1 “交换”到数组的前部。
算法的核心逻辑可以分解为以下步骤:
-
指针定义:
- 算法使用两个指针,
loc0
和loc1
,来追踪已排序区域的边界。 loc0
:表示下一个 0 应该被放置的位置。换句话说,[0, loc0-1]
区域内保证全部是 0。loc1
:表示下一个 1 应该被放置的位置。换句话说,[0, loc1-1]
区域内保证全部是 0 或 1。- 一个隐形的第三区域是由遍历指针
i
构成的,可以认为[loc1, i-1]
区域在任何时刻都填充的是 2。
- 算法使用两个指针,
-
单次遍历与“覆盖”逻辑:
- 算法通过一个
for
循环,从左到右遍历整个数组。对于遇到的每个数字nums[i]
,它执行一个非常巧妙的“先写后改”的覆盖操作。 - 关键步骤
nums[i] = 2;
:在读取nums[i]
的值存入temp
后,算法立即将nums[i]
的位置用 2 覆盖。这可以理解为:我们把当前位置i
“清空”并暂时标记为 2,因为我们即将把temp
的值(如果是0或1)移动到它在数组前面应在的正确位置。 - 处理 0 和 1 (
if (temp <= 1)
):如果原始数字temp
是 0 或 1,它就不应该留在当前这个可能是“2”的区域。它至少应该是一个 1。因此,算法在loc1
的位置写入一个 1,并将loc1
指针右移。 - 处理 0 (
if (temp == 0)
):如果原始数字temp
是 0,它不仅需要被移到前面,还必须被移到loc0
的位置。因此,算法在loc0
的位置写入一个 0,并将loc0
指针右移。 - 巧妙的覆盖:注意,当
temp
为 0 时,会先执行nums[loc1++] = 1
,然后执行nums[loc0++] = 0
。因为loc0 <= loc1
恒成立,所以后一步的nums[loc0] = 0
会覆盖掉前一步在loc0
位置写入的1
。这正是算法的精髓所在:当一个 0 被发现时,它占据了loc0
的位置,而原本可能在这里的 1 被有效地“推”到了后面 1 的区域的开头,即loc1
的位置。
- 算法通过一个
-
最终状态:
- 当
for
循环结束后,loc0
和loc1
已经正确地将数组划分为了三部分:[0, loc0-1]
全是 0,[loc0, loc1-1]
全是 1,[loc1, n-1]
全是 2。
- 当
完整代码
class Solution {/*** 对包含 0, 1, 2 三种元素的数组进行原地排序。* @param nums 整数数组*/public void sortColors(int[] nums) {// loc0: 指向下一个 0 应该被放置的位置。int loc0 = 0;// loc1: 指向下一个 1 应该被放置的位置。int loc1 = 0;// 单次遍历整个数组for (int i = 0; i < nums.length; i++) {// 1. 保存当前位置的原始值int temp = nums[i];// 2. 关键步骤:先用 2 覆盖当前位置。// 这相当于把当前位置 i 暂时归入 "2" 的区域,// 为即将被前移的 0 或 1 腾出空间。nums[i] = 2;// 3. 如果原始值是 0 或 1,它需要被移动到前面。// 我们先在 loc1 位置放置一个 1,并将 loc1 右移。// 这确保了 [0, loc1-1] 区域至少是 0 或 1。if (temp <= 1) {nums[loc1++] = 1;}// 4. 如果原始值是 0,它需要被移动到更前面。// 我们在 loc0 位置放置一个 0,并将 loc0 右移。// 如果 temp 是 0,这一步会覆盖掉上一步在 loc0 位置放置的 1。// 这是正确的,因为 0 的优先级更高,而 1 被有效地“推”到了 1 的区域。if (temp == 0) {nums[loc0++] = 0;}}}
}
时空复杂度
时间复杂度:O(N)
- 循环:算法的核心是一个
for
循环,它从i = 0
遍历到nums.length - 1
。这个循环严格地执行N
次,其中N
是数组的长度。 - 循环内部操作:
- 在循环的每一次迭代中,执行的都是固定数量的操作:一次数组读,最多三次数组写,几次比较和指针增量。
- 这些操作的时间复杂度都是 O(1)。
综合分析:
算法由 N
次 O(1) 的操作组成。因此,总的时间复杂度是 N * O(1)
= O(N)。
空间复杂度:O(1)
- 主要存储开销:该算法没有创建任何与输入规模
N
成比例的新的数据结构(如辅助数组)。 - 辅助变量:只使用了
loc0
,loc1
,i
,temp
等几个固定数量的整型变量。
综合分析:
算法是在原数组上进行修改的(in-place),所需的额外辅助空间是常数级别的。因此,其空间复杂度为 O(1)。
参考灵神