「这是我参与2022首次更文挑战的第30天,活动详情查看:2022首次更文挑战」。
题目
给定一个包含红色、白色和蓝色、共 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
进阶:
- 你可以不使用代码库中的排序函数来解决这道题吗?
- 你能想出一个仅使用常数空间的一趟扫描算法吗?
思考
这是一道有意思的题目,难度中等。
本题是经典的「荷兰国旗问题」,由计算机科学家 Edsger W. Dijkstra 提出。荷兰国旗是由红白蓝3种颜色的条纹拼接而成。假设这样的条纹有多条,且各种颜色的数量不一,如何把这些条纹按照颜色排好,红色的在上半部分,白色的在中间部分,蓝色的在下半部分,我们把这类问题称作荷兰国旗问题。
回到题目中,我们可以考虑统计数组中数字0、1、2的数量,然后依次存入新的数组中即可。考虑到需要原地修改原数组,那么我们应该怎么操作呢?
我们可以使用单指针的方法。定义指针p,当数组元素是0时,交换指针和当前元素索引,然后继续遍历数组。之后,再次遍历数组,判断数组元素是否为1。
此外,我们可以使用双指针的方法。上述方法需要遍历两次数组,借助双指针我们可以一次遍历就实现。当遍历到的元素是0时,交换到数组开头;元素为2时,交换到数组末尾。考虑到元素为2时,一次交换后元素可能仍然为2,那么我们可以使用while循环继续判断并交换。
本题中是原地修改数组,因此不需要设置返回值。
解答
方法一:单指针
/**
* @param {number[]} nums
* @return {void} Do not return anything, modify nums in-place instead.
*/
var sortColors = function(nums) {
let p = 0
for (let i = 0; i < nums.length; i++) {
if (nums[i] === 0) {
[nums[i], nums[p]] = [nums[p], nums[i]]
p++
}
}
for (let i = p; i < nums.length; i++) {
if (nums[i] === 1) {
[nums[i], nums[p]] = [nums[p], nums[i]]
p++
}
}
}
复杂度分析
- 时间复杂度:O(n),其中 n 是数组 nums 的长度。
- 空间复杂度:O(1)。
方法二:双指针
/**
* @param {number[]} nums
* @return {void} Do not return anything, modify nums in-place instead.
*/
var sortColors = function(nums) {
let len = nums.length
let p0 = 0, p2 = len - 1
for (let i = 0; i < len; i++) {
while (i <= p2 && nums[i] === 2) {
// 交换之后nums[i]可能仍为2,因此需要while循环继续判断
[nums[i], nums[p2]] = [nums[p2], nums[i]]
p2--
}
if (nums[i] === 0) {
[nums[i], nums[p0]] = [nums[p0], nums[i]]
p0++
}
}
}
复杂度分析
- 时间复杂度:O(n),其中 n 是数组 nums 的长度。
- 空间复杂度:O(1)。