LeetCode探索(44):75-颜色分类

133 阅读1分钟

「这是我参与2022首次更文挑战的第30天,活动详情查看:2022首次更文挑战」。

题目

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

我们使用整数 012 分别表示红色、白色和蓝色。

必须在不使用库的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.length
  • 1 <= n <= 300
  • nums[i]012

进阶:

  • 你可以不使用代码库中的排序函数来解决这道题吗?
  • 你能想出一个仅使用常数空间的一趟扫描算法吗?

思考

这是一道有意思的题目,难度中等。

本题是经典的「荷兰国旗问题」,由计算机科学家 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)。

参考