给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
注意:
不能使用代码库中的排序函数来解决这道题。
示例:
输入: [2,0,2,1,1,0] 输出: [0,0,1,1,2,2]
进阶:
- 一个直观的解决方案是使用计数排序的两趟扫描算法。
首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。 - 你能想出一个仅使用常数空间的一趟扫描算法吗?
思路一:一次遍历,用三个变量n0,n1,n2分别表示访问到当前下标i时数组中已排序的0,1,2的最右端,如下图所示:
那么有同学就要问了,你这是已经i-1之前的元素已经排序以后的结果指向当然正确了,如果没有排序呢?那么我们就来看下当前下标是如何处理的,因为处理当前的下标的代码如下所示:
if (nums[i] == 0) {
nums[++n2] = 2;
nums[++n1] = 1;
nums[++n0] = 0;
}
else if (nums[i] == 1) {
nums[++n2] = 2;
nums[++n1] = 1;
}
else {
nums[++n2] = 2;
}
因为当前下标的值是1,所以先把n2+1即n2=7,然后赋值为2,同理n1+1后赋值为1,处理过后如图所示:
这种方法很巧,相当于通过记录当前n0,n1,n2的右边界,每次根据判断当前下标的值把n0,n1,n2向右移动,这里要注意移动的顺序,必须是先n2,再n1,最后n0,否则会出现数据覆盖的问题。
参考代码:
class Solution {
public:
void sortColors(vector<int>& nums) {
int n2 = -1, n1 = -1, n0 = -1,n=nums.size();
for (int i = 0; i < n; i++) {
if (nums[i] == 0) {
nums[++n2] = 2;
nums[++n1] = 1;
nums[++n0] = 0;
}
else if (nums[i] == 1) {
nums[++n2] = 2;
nums[++n1] = 1;
}
else {
nums[++n2] = 2;
}
}
}
};
思路二:采用荷兰三色旗染色问题,具体思路是把需要染色的部分分成四个部分:
- a[1..Lo-1] zeroes (red)
- a[Lo..Mid-1] ones (white)
- a[Mid..Hi] unknown
- a[Hi+1..N] twos (blue)
这里红色表示1,白色表示1,蓝色表示2,我们的目标便是把未知区域的空间压缩到0,初始化lo=0,Mid=0,Hi为nums.size()-1,伪代码如下:
- Lo := 1; Mid := 1; Hi := N;
- while Mid <= Hi do
- Invariant: a[1..Lo-1]=0 and a[Lo..Mid-1]=1 and a[Hi+1..N]=2; a[Mid..Hi] are unknown.
- case a[Mid] in
- 0: swap a[Lo] and a[Mid]; Lo++; Mid++
- 1: Mid++
- 2: swap a[Mid] and a[Hi]; Hi–
意思是:每次都判断a[Mid]:
如果是0,那么他应该归属第一区域,所以交换a[Mid]和a[Lo],然后Mid++,Lo++,这样第一区域[0,Lo-1]就包含0
如果是1,应该在第二区域,那么Mid++,这样能保证在第二区域[Lo,Mid-1],而不会在未知区域
如果是2,那么交换a[Mid]和a[Hi],然后Hi--,这样能保证2落在第四区域[Hi+1,N],而不会落在未知区域[Mid,Hi]
这样不断循环知道未知区域的范围是0,便可以完成对三个颜色的划分
参考代码:
class Solution {
public:
void sortColors(vector<int>& nums) {
int lo = 0, mid = 0, hi = nums.size() - 1;
while (mid <= hi) {
if (nums[mid] == 0) {
swap(nums[mid], nums[lo]);
mid++;
lo++;
}
else if (nums[mid] == 1) {
mid++;
}
else {
swap(nums[mid], nums[hi]);
hi--;
}
}
}
};