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
为数组长度;- 指针
p0
和p2
分别为小于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
*/