Loading

leetcode 75. Sort Colors 中等

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sort-colors

题目描述

Given an array nums with n objects colored red, white, or blue, sort them in-place so that objects of the same color are adjacent, with the colors in the order red, white, and blue.

We will use the integers 0, 1, and 2 to represent the color red, white, and blue, respectively.

Example 1:

Input: nums = [2,0,2,1,1,0]
Output: [0,0,1,1,2,2]

Example 2:

Input: nums = [2,0,1]
Output: [0,1,2]

Example 3:

Input: nums = [0]
Output: [0]

Example 4:

Input: nums = [1]
Output: [1]

Constraints:

n == nums.length
1 <= n <= 300
nums[i] is 0, 1, or 2.

Follow up:

Could you solve this problem without using the library's sort function?
Could you come up with a one-pass algorithm using only O(1) constant space?

法一:计数排序

1. 思路

关于计数排序

由于只有3种数字,我们很容易想到计数排序。

2. 代码

class Solution {
public:
    void sortColors(vector<int>& nums) {
        int counts[3] = {0};	// 1. 建立0-2的额外数组,保存每个数字出现的次数。
        int size = nums.size();
        for (int i = 0; i < size; i++) {	// 2. 统计每个数字出现的次数
            counts[nums[i]]++;
        }

        int k = 0;
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < counts[i]; j++) {	// 3. 按照顺序填充到原数组
                nums[k++] = i;
            }
        }
    }
};

3. 分析

相当于扫描了两遍数组,故时间复杂度为 \(O(2n)\).

空间复杂度为使用额外数组的大小,\(O(k)\). k为数组中元素最大和最小值的差。


法二:双指针(利用快排中的partition)

1. 思路

本题是由dijkstra提出的经典的荷兰国旗问题,三向切分问题。

在该问题中,核心是定义循环不变量——“声明的变量在遍历的过程中需要保持定义不变。”)

设:

  • len为数组长度;
  • 指针p0p2分别为小于0和大于2的分界点;
  • 变量i为循环变量,一般为开区间,表示已经循环了前i个元素。

则定义循环不变量如下:

  • [0, p0)都是为0的元素;
  • [p0, i)都是为1的元素;
  • [p2, len-1]都是为2的元素。

接下来考虑:

  • 变量初始化应该如何定义?应该保证上述区间均恰好为空。
  • 在遍历的时候,是先加减还是先交换?根据变量初始化的位置而定。
  • 什么时候循环终止?当i<p2时,恰好覆盖整个数组。若改为[p0, i)(p2, len-1],则就要再进行一次i==p2循环才能覆盖整个数组。

2. 代码

class Solution {
public:

    void swapNum(vector<int>& nums, int a, int b) {
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }

    void sortColors(vector<int>& nums) {
        /**
        * [0, p0)都是为0的元素;
        * [p0, i)都是为1的元素;
        * [p2, len-1]都是为2的元素。
        */
        int len = nums.size();
        int p0 = 0, p2 = len;
        int i = 0;
        while (i < p2) {
            if (nums[i] == 0) {
                swapNum(nums, p0, i);
                p0++;
                i++;
            } else if (nums[i] == 1) {
                i++;
            } else {
                // nums[i] == 2
                p2--;
                swapNum(nums, p2, i);
            }
        }
    }
};

3. 注意点

注意在初始化各个变量后,再进行遍历时操作的考虑。

法三:双指针(上面的变种)

1. 思路

与法二的区别时,这里设置循环不变量时,设置的两个边界指针关于0和1.

  • [0, p0)都是为0的元素;
  • [p0, p1)都是为1的元素;
  • (i, len)都是为2的元素。

2. 代码

class Solution {
public:

    void swapNum(vector<int>& nums, int a, int b) {
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }

    void sortColors(vector<int>& nums) {
        /**
        * `[0, p0)`都是为0的元素;
        * `[p0, p1)`都是为1的元素;
        * `(i, len)`都是为2的元素。
        */
        int len = nums.size();
        int p0 = 0, p1 = 0;
        int i = len - 1;
        while (p1 <= i) {
            if (nums[i] == 0) {                
                swapNum(nums, p0, i);
                if(p0 == p1)	// 易错点!!!
                    p1++;
                p0++;
            } else if (nums[i] == 1) {
                swapNum(nums, p1, i);
                p1++;
            } else {
                // nums[i] == 2
                i--;
            }
        }
    }
};

3. 注意点

在上面代码中,有一个易错点,简单描述:若出现p0 < p1的情况,则说明在排好的0之后,有排好的1,这种情况下指针不能同时增加,那样会导致会漏掉一些情况。如果不添加if语句则:

/**
 * 2 0 2 1 1 0	// p0=0,p1=0,i=5
 * 0 0 2 1 1 2	// p0=1,p1=1,i=4
 * 0 1 2 1 0 2 	// p0=1,p1=2,i=4	// 这时若p0,p1同时增加则
 * 0 0 2 1 1 2	// p0=2,p1=3,i=4
 * 0 0 2 1 1 2	// p0=2,p1=4,i=3	// 结束 答案错误 由于p0,p1同增,忽略掉了p1指向的2
 */
posted @ 2021-03-30 19:59  seigann  阅读(24)  评论(0)    收藏  举报