LeetCode Hot100 75. 颜色分类

53 阅读3分钟

一. 题目

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

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

必须在不使用库内置的 sort 函数的情况下解决这个问题。

 

示例 1:

输入: nums = [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]

示例 2:

输入: nums = [2,0,1]
输出: [0,1,2]

 

提示:

  • n == nums.length
  • 1 <= n <= 300
  • nums[i] 为 01 或 2

二. 解析

时间复杂度均为o(n),空间复杂度均为o(1)

一. 双指针+就地分区

思路:维护两个边界指针,把区间分成:

  • [0, whiteIndex) 都是 0
  • [whiteIndex, blueIndex) 都是 1
  • [blueIndex, i) 都是 2
  • [i, end) 未处理

当遇到:

  • 0:先和 whiteIndex 交换把 0 放前面;如果原来 whiteIndex < blueIndex,说明被你换到 i 的是个 1,还要再和 blueIndex 交换把 1 接到 1 区末尾;然后两个指针都右移。
  • 1:和 blueIndex 交换,让 1 接到 1 区末尾,blueIndex++
  • 2:不动,i 继续,2 自然堆在中间区间。

这样做到单向扫描,0最多2次交换,1最多1次,2不需要动

看代码

var sortColors = function (nums) {
    // 指向1区开头的下标
    let whiteIndex = 0;
    // 指向2区开头的下标
    let blueIndex = 0;
    for (let i = 0; i < nums.length; i++) {
        switch (nums[i]) {
            case 0:
                [nums[i], nums[whiteIndex]] = [nums[whiteIndex], nums[i]];
                // 当whiteIndex < blueIndex的时候,说明换到的i是个1,还需要把它和blueIndex也就是2的开头交换
                if (whiteIndex < blueIndex)
                    [nums[i], nums[blueIndex]] = [nums[blueIndex], nums[i]];
                whiteIndex++;
                blueIndex++;
                break;
            case 1:
                [nums[i], nums[blueIndex]] = [nums[blueIndex], nums[i]];
                blueIndex++;
                break;
            default:
                break;
        }
    }
};

二. 三指针(荷兰国旗三指针)

思路:用三个指针low,mid, high分别指向0区,1区末尾,2区首位的前一个,让mid指向的作为行动指针,碰到0的时候,和low指向的交换,lowmid自增,碰到1的时候,mid自增,碰到2的时候,和high指向的交换,然后再判断这个交换过来的数字

这样也是单向扫描,每步只做一次交换,但需要双向夹逼

看代码

var sortColors = function (nums) {
    // low mid从开头开始,指向0,1区的末尾,high2区的开头
    let low = 0, mid = 0, high = nums.length - 1;
    while (mid <= high) {
        if (nums[mid] === 0) {
            [nums[low], nums[mid]] = [nums[mid], nums[low]];
            low++;
            mid++;
        } else if (nums[mid] === 1) {
            mid++;
        } else{
            // 如果是2,把它换到2区的开头,作为首位置,然后再把换过来的这个数字再次检查一遍
            [nums[high], nums[mid]] = [nums[mid], nums[high]];
            high--;
        }
    }
};

三. 计数排序

思路:分别计算0,1,2出现的次数,然后按次数直接覆盖,思路很简单且直观

这样是遍历两次

看代码

var sortColors = function (nums) {
    let c0 = 0, c1 = 0, c2 = 0;
    // 计数
    for (const num of nums) num === 0 ? c0++ : num === 1 ? c1++ : c2++;
    let i = 0;
    // 覆盖
    while (c0--) nums[i++] = 0;
    while (c1--) nums[i++] = 1;
    while (c2--) nums[i++] = 2;
};