颜色分类
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地**对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
示例:
输入:** [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]</pre>
进阶:
- 一个直观的解决方案是使用计数排序的两趟扫描算法。
首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。 - 你能想出一个仅使用常数空间的一趟扫描算法吗?
题目分析
- 相同元素相邻,换句话说即分区存放,一个颜色一个区域,但因为这里颜色使用0 ,1,2表示,即对一个012的无序序列进行排序,变成000...111....2222的顺序。
正常排序算法都适用,只不过这里注意仅有三种可能的顺序值,可以有特殊做法。
- 对于一个元素,我们将他分区时,肯定是插在区域的末尾,换句话说,我们只要保留每个区域的末尾下标,插入的时候交换过去就可以了。
三个区域,其实我们只要确定了两个区域即可确定第三个区域,因为0区域在最左边,2区域在最右边,方便程序定位,因此取0 2 的末尾指针保留下来。
- 确定了双指针确定区域,那么怎么进行遍历处理呢?确定了分类移动的位置,即cur遍历每一位的数值,分类处理遇到0放左边,遇到2放右边,02放好了,则1的区域也放好了。
针对cur的分类处理:
是0,则和0的flag ,left交换值,同时left++。此时cur获取的是之前的left交换过来的值。我们知道我们换过去的处理好了,那么这个换过来的还需要处理吗?目前还不知道,先看2的情况的处理。
是2,则和2的flag,right交换,同时right--。同样,此时cur获取了交换过来的值,由于这个值必然是在cur右边(因为它是原right,即cur的右边界),则cur肯定没有遍历过这个值。
同时因为它在cur右边,它也不可能是交换过去的,因为交换过去的值肯定都在0 2 区域里了。
因此这个值是野生的,所以cur在此停留,继续处理这个交换过来的值。是1,因为我们把0 2 排序好,1的区域自然就出来了,因此1直接不处理,过就好了
-
那么看看0的情况交换过来的值?首先这个值的位置关系,即原left是在cur左边还是右边呢?
只有当left会在cur右边时,left指向的才可能是cur没处理过的,但此时cur在left内部?那cur就是把一个0换到了外边,把一个乱序的换进了0的内部,内鬼毫无意义。
当left在cur左边时,即left指向的必定经过了cur的洗礼,而且它必不是原来在后面被换到前面的,因为交换的肯定在left内部去了。所以它原值就是在那,且经过处理不用动,因此从left换到现在的cur之后,也是同样的处理,也不用动它,cur++拜拜嘞。
当cur遇到right,即到达2的边界,则说明不需要处理了,结束。
题解代码
class Solution {
public:
void sortColors(vector<int>& nums)
{
int left=0;
int right=nums.size()-1;
int cur=0;
while(cur<=right)
{
if(nums[cur]==0)//这个数字是0,移动0区域内,
{
swap(nums[cur],nums[left]);
//假如cur>left,left交换过来的值肯定已经被处理过了,但是处理时产生交换,原left指向的,一定是不用处理的吗?
//jeromememory:准确的说 cur 如果 与 p0 不是指向同一个索引,那 cur 指向的索引值如果发生交换,那交换过来的一定是 1(原因是只有当遍历过的节点有1,p0 和 cur 才不会同步),而 如果索引是 1 刚好也就不用有任何操作,所以可以直接++。
//假如cur==left==0,没啥好说的,下一个
//假如cur<left, 可能吗?cur++的机会 >= left++的机会
left++;
}
else if(nums[cur]==2)//这个数字是2,移到2区域内
{
swap(nums[cur],nums[right]);
right--;
//交换过来的值,是右边过来的,cur没处理过,因此还需要对这个位置处理,--抵消++,位置不变
cur--;
}
cur++;
}
}
};