算法学习-颜色分类

问题

题目出自Leetcode:

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

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

进阶:

一个直观的解决方案是使用计数排序的两趟扫描算法。 首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。
你能想出一个仅使用常数空间的一趟扫描算法吗?

分析

这个问题本质上就是排序,因此方法很多。但是我这里对“进阶”中所说的“仅使用常数空间的一趟扫描算法”这个条件很感兴趣,所以尝试着去实现这个条件下的方法。

我们可以注意到,这个问题里的数组元素只有3种:0、1、2(当然,就算是其它的也行,关键是只有“3种”)。所以可以这么考虑,对数组进行扫描,遇到2的时候,我们就把它移出来,然后扔到数组最后。遇到0的时候,我们也把它移出来,扔到数组最前。

这么扫描操作一轮之后,实际上数组就已经按照0、1、2的顺序排好序了。直接在原数组上操作即可,也不需要额外的数组和空间。

实现

先实现一个数组生成器吧,用来生成任意长度的随机数组,数组元素可能是0、1、2。

function createArr(len) {
    return new Array(len).fill(null).map(() => parseInt(Math.random() * 3))
} 

然后是排序函数:

function sort(arr) {
    let len = arr.length;
    let pushCount = 0;
    for (let i = 0; i < len; i++) {
        if (pushCount >= len - i) break; // 在把2往后扔的时候,我们对2进行计数. 如果剩余的长度跟2的个数已经一致了,就可以判断出后面已经全部是2了,没有别的可能了
        if (arr[i] === 2) {
            for (let j = i; j < len; j++) {
                arr[j] = arr[j + 1];
            }
            arr[len - 1] = 2;
            pushCount ++;
            i--;
        } else {
            if (arr[i] === 0 && i != 0) {
                for (let j = i - 1; j >= 0; j--) {
                    arr[j + 1] = arr[j];
                }
                arr[0] = 0;
            }
        }
    }
    return arr
}

这里需要注意几个点:
1)如果第一个元素就是0,那就别往数组最前面扔了,因为人家本来就是在最前面。

2)在把2往数组最后扔的时候,后面的元素会前移。也就是说,2后面的元素会移动到当前位置,这时需要对这个新移动过来的元素也进行一次判断,所以循环的索引标记 i 不能马上自增1。我们在将2扔入数组末尾的同时,让 i 先自减少1,然后在循环条件里 i 自增1 ,先减后增,就相互抵消了,相当于i没变,仍然停留在当前索引下的元素进行操作。

i - -;

3)我们其实不会把整个数组全部扫描完毕。想一想,在把2往数组最后扔的时候,到一定程度,数组最后就全是被我们扔过去的2了。这时实际上排序已经完成,没必要继续扫描了。通过定义一个pushCount变量,用来记录我们扔了多少次的2。每扔一次2,就让pushCount加1。当满足pushCount等于数组长度减去当前索引的时候,就可以停止扫描了。数组长度减去当前索引就是“数组剩余的元素”,如果剩余的元素跟我们扔2的次数(实际就是2的个数)一致,那意思不就是“剩余的元素已经全都是2”了么?此时整个排序结束。

if (pushCount >= len - i) break;

尝试

来试一下,先生成一个随机乱序的数组:

let arr = createArr(100)
输出:
[
  1, 0, 0, 2, 1, 0, 0, 1, 0, 1, 1, 0,
  2, 0, 2, 2, 2, 1, 2, 0, 1, 2, 2, 2,
  1, 0, 1, 1, 0, 0, 0, 1, 1, 2, 0, 0,
  1, 1, 2, 2, 0, 1, 0, 2, 0, 1, 0, 1,
  1, 1, 2, 2, 1, 1, 2, 1, 2, 2, 0, 0,
  2, 2, 2, 2, 2, 1, 0, 2, 2, 2, 0, 2,
  1, 0, 0, 2, 1, 0, 2, 0, 2, 2, 0, 1,
  2, 1, 0, 0, 1, 1, 0, 0, 1, 2, 1, 1,
  2, 2]

排序:

sort(arr)
结果:
[
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2,
  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
  2, 2, 2, 2
]

版权声明:本文为TheJormangund原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。