Leetcode 75.颜色分类


题目描述:

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

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

进阶

  • 你可以不使用代码库中的排序函数来解决这道题吗?
  • 你能想出一个仅使用常数空间的一趟扫描算法吗?

示例 1:

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

示例 2:

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

示例 3:

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

示例 4:

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

提示:

  • n == nums.length
  • 1 <= n <= 300
  • nums[i]012

链接:

https://leetcode-cn.com/problems/sort-colors


题目分析

1.计数

  题目中要求使用原地算法,并且最好不用排序函数,而数字只有三种,我们可以很容易想到使用计数的方法。先遍历一次数组记录 01 的个数,剩下的就是 2,之后对整个数组进行重写即可。一共需要两次遍历。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
void sortColors(vector<int>& nums) {
int red = 0, white = 0;
for(int i = 0; i < nums.size(); i++){
if(nums[i] == 0) red++;
else if(nums[i] == 1) white++;
}
for(int i = 0; i < red; i++){
nums[i] = 0;
}
for(int i = red; i < red + white; i++){
nums[i] = 1;
}
for(int i = red + white; i < nums.size(); i++){
nums[i] = 2;
}
}
};

2.双指针

  由于只有三种数字,最后的结果中 0 一定在左边,2 一定在右边。那我们只进行一次遍历,每次将 0 放到左边,2 放到右边,最后剩下的 1 就都在中间,也就完成了。我们需要两个指针 p0p2 分别指示 02 的边界位置。
  注意我们是从左往右遍历,而 p2 是位于右边,因此与 p2 交换后,i 所指示的位置的新数字依然是没有处理过的,因此 i 不能自增。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
void sortColors(vector<int>& nums) {
int p0 = 0, p2 = nums.size()-1;
int i = 0;
while(i <= p2){
if(nums[i] == 2){
swap(nums[i], nums[p2]);
p2--;
}
else{
if(nums[i] == 0){
swap(nums[i], nums[p0]);
p0++;
}
i++;
}
}
}
};

3.官方题解

  官方题解中还提到了其他同样使用指针的方法。一种是单指针的方法,先遍历一次处理 0(遇到 0 就交换到最左边),再从 0 结束的位置遍历一次处理 1,一共需要两次遍历。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
void sortColors(vector<int>& nums) {
int n = nums.size();
int ptr = 0;
for (int i = 0; i < n; ++i) {
if (nums[i] == 0) {
swap(nums[i], nums[ptr]);
++ptr;
}
}
for (int i = ptr; i < n; ++i) {
if (nums[i] == 1) {
swap(nums[i], nums[ptr]);
++ptr;
}
}
}
};

  还有一种是另外一种双指针的方法,记录的是 01 的边界位置,放入 0 时两个指针同时右移(若有 10 替换还需在末尾补 1),放入 1p1 右移,这种方法也是一次遍历。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
void sortColors(vector<int>& nums) {
int n = nums.size();
int p0 = 0, p1 = 0;
for (int i = 0; i < n; ++i) {
if (nums[i] == 1) {
swap(nums[i], nums[p1]);
++p1;
} else if (nums[i] == 0) {
swap(nums[i], nums[p0]);
if (p0 < p1) {
swap(nums[i], nums[p1]);
}
++p0;
++p1;
}
}
}
};

复杂度分析

  以上的所有方法不管是两次遍历还是一次遍历,时间复杂度都是 $O(n)$,而作为原地算法,空间复杂度都是 $O(1)$。