[ 路飞] 颜色分类

581 阅读2分钟

题目描述

给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

解题思路

算法

双指针,p0 表示下一个 0 放置的位置,p2 表示下一个 2 放置的位置

思路

本题的关键在于循环不变量,也就是我们在循环中需要遵循的条件。

首先,在双指针以外,我们需要一个 i 存放遍历的 index

本体的循环不变量是:

[0, p0) = 0
[p0, i] = 1
(p2, r) = 2

其中 r 是我们数组的右边界,在代码中,我们把它替换成 nums.length - 1
可见在遍历中,p0,p2 指向的元素并不是 0,2

我们会在遍历的过程中不断的交换 nums[i], nums[p0] 或者 nums[i], nums[p2],循环的终止条件是 i <= p2,也就是当我遍历到 2 的区间时,循环应当退出,因此此时 0,1,2 三个值已经都排好了

这个条件的关键点在于是否需要加这个 =

我们来看这么一种情况

2(p0, i), 0, 1(p2)

首先 ip2 的元素交换 ➡️ 1(i, p0), 0(p2), 2

之后 i++ ➡️ 1, 0(i, p2), 2 循环退出

明显可得,此时的数组不和要求,因此在当前我们所设的循环不变量的条件下,循环终止的条件是 i <= p2

代码

/**
 * @param {number[]} nums
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var sortColors = function(nums) {
    let p0 = 0, p2 = nums.length - 1, i = 0

    // loop invariant
    /**
        [0, p0) = 0
        [p0, i] = 1
        (p2, num.length - 1) = 2
     */

    while (i < p2) {
        if (nums[i] === 1) {
            i++
        } else if (nums[i] === 0) {
            ;[nums[p0], nums[i]] = [nums[i], nums[p0]]
            p0++
            i++
        } else if (nums[i] === 2) {
            ;[nums[p2], nums[i]] = [nums[i], nums[p2]]
            p2--
        }
    }
};