Mobile wallpaper 1Mobile wallpaper 2Mobile wallpaper 3Mobile wallpaper 4Mobile wallpaper 5Mobile wallpaper 6Mobile wallpaper 7Mobile wallpaper 8
813 字
4 分钟
算法每日一题:数组分区与指针推进

目录#

颜色分类只有 0、1、2 三种值,直接排序当然能得到结果,但题目要求不使用库内置排序,并尽量只扫描一遍。要满足这个限制,可以把数组划成几个已经确定的区域,再不断缩短待处理区域。

一、四个区域#

LeetCode 75. 颜色分类

使用 left、current、right 三个指针,把数组分成四个部分:

区间含义
[0, left]已确定的 0
[left + 1, current - 1]已确定的 1
[current, right - 1]待处理区域
[right, nums.length - 1]已确定的 2

初始时 left = -1current = 0right = nums.length,前三个已确定区域都是空的,整个数组都在待处理区域中。每一步只需要检查 nums[current],再把它丢给对应的区域即可。

二、快排的分区思路#

这个过程可以联想到快速排序的数组分区。快排选择一个基准值,不断把较小的元素放到左侧、较大的元素放到右侧,最后让基准值落在分界位置。

颜色分类已经知道三种元素的归属,不需要额外选择基准值:

  • 遇到 0,与左侧边界交换,然后扩大 0 区域。
  • 遇到 2,与右侧边界交换,然后扩大 2 区域。
  • 遇到 1,它本来就属于中间区域,只需推进 current

分区的核心是区间含义始终不变。每次交换和移动指针后,四个区间仍然满足表中的定义。

三、错误的统一推进#

初步写法可以把 current 放进 for 循环,每轮统一加一:

for (int current = 0; current < right; current++) {
if (nums[current] == 0) {
swap(nums, current, ++left);
} else if (nums[current] == 2) {
swap(nums, current, --right);
}
}

问题会在 [1, 2, 0] 中出现。current = 1 时,2 与右侧的 0 交换,数组变成 [1, 0, 2]。下一轮直接执行 current++,刚刚换到当前位置的 0 没有再次处理。

这说明了一个问题:交换来的数据也可能需要重新判断。

四、交换后的判断#

什么时候可以继续推进,什么时候必须留在原地?要看交换位置属于哪个区域。

遇到 0 时,交换目标是新的 left。这个位置原本位于已确定的 1 区域,或者恰好就是 current,因此交换后当前位置已经处理完,可以执行 current++

遇到 2 时,交换目标是 --right。这个位置原本属于待处理区域,换来的值可能是 0、1、2 中的任何一个,所以 current 不能前进。代码中使用 continue,让下一轮继续判断同一位置。

class Solution {
public void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
public void sortColors(int[] nums) {
int left = -1;
int right = nums.length;
int current = 0;
while (current < right) {
if (nums[current] == 0) {
swap(nums, current, ++left);
} else if (nums[current] == 2) {
swap(nums, current, --right);
continue;
}
current++;
}
}
}

current 只向右移动,right 只向左移动,每一步都会缩短待处理区域,因此时间复杂度为 O(n)。交换在原数组中完成,额外空间复杂度为 O(1)

算法每日一题:数组分区与指针推进
https://zhuanlan.zhihu.com/p/2073032285104911762
作者
Mem0rin
发布于
2026-08-18
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时