一. 题目
给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地** 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
必须在不使用库内置的 sort 函数的情况下解决这个问题。
示例 1:
输入: nums = [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]
示例 2:
输入: nums = [2,0,1]
输出: [0,1,2]
提示:
n == nums.length1 <= n <= 300nums[i]为0、1或2
二. 解析
时间复杂度均为o(n),空间复杂度均为o(1)
一. 双指针+就地分区
思路:维护两个边界指针,把区间分成:
[0, whiteIndex)都是 0[whiteIndex, blueIndex)都是 1[blueIndex, i)都是 2[i, end)未处理
当遇到:
0:先和whiteIndex交换把 0 放前面;如果原来whiteIndex < blueIndex,说明被你换到i的是个 1,还要再和blueIndex交换把 1 接到 1 区末尾;然后两个指针都右移。1:和blueIndex交换,让 1 接到 1 区末尾,blueIndex++。2:不动,i继续,2 自然堆在中间区间。
这样做到单向扫描,0最多2次交换,1最多1次,2不需要动
看代码
var sortColors = function (nums) {
// 指向1区开头的下标
let whiteIndex = 0;
// 指向2区开头的下标
let blueIndex = 0;
for (let i = 0; i < nums.length; i++) {
switch (nums[i]) {
case 0:
[nums[i], nums[whiteIndex]] = [nums[whiteIndex], nums[i]];
// 当whiteIndex < blueIndex的时候,说明换到的i是个1,还需要把它和blueIndex也就是2的开头交换
if (whiteIndex < blueIndex)
[nums[i], nums[blueIndex]] = [nums[blueIndex], nums[i]];
whiteIndex++;
blueIndex++;
break;
case 1:
[nums[i], nums[blueIndex]] = [nums[blueIndex], nums[i]];
blueIndex++;
break;
default:
break;
}
}
};
二. 三指针(荷兰国旗三指针)
思路:用三个指针low,mid, high分别指向0区,1区末尾,2区首位的前一个,让mid指向的作为行动指针,碰到0的时候,和low指向的交换,low和mid自增,碰到1的时候,mid自增,碰到2的时候,和high指向的交换,然后再判断这个交换过来的数字
这样也是单向扫描,每步只做一次交换,但需要双向夹逼
看代码
var sortColors = function (nums) {
// low mid从开头开始,指向0,1区的末尾,high2区的开头
let low = 0, mid = 0, high = nums.length - 1;
while (mid <= high) {
if (nums[mid] === 0) {
[nums[low], nums[mid]] = [nums[mid], nums[low]];
low++;
mid++;
} else if (nums[mid] === 1) {
mid++;
} else{
// 如果是2,把它换到2区的开头,作为首位置,然后再把换过来的这个数字再次检查一遍
[nums[high], nums[mid]] = [nums[mid], nums[high]];
high--;
}
}
};
三. 计数排序
思路:分别计算0,1,2出现的次数,然后按次数直接覆盖,思路很简单且直观
这样是遍历两次
看代码
var sortColors = function (nums) {
let c0 = 0, c1 = 0, c2 = 0;
// 计数
for (const num of nums) num === 0 ? c0++ : num === 1 ? c1++ : c2++;
let i = 0;
// 覆盖
while (c0--) nums[i++] = 0;
while (c1--) nums[i++] = 1;
while (c2--) nums[i++] = 2;
};