Description

給定陣列 nums,其中只包含 0、1、2(代表紅、白、藍),原地排序使得相同顏色相鄰,順序為 0、1、2。不能使用內建排序函式。

Example:

Input: nums = [2,0,2,1,1,0] Output: [0,0,1,1,2,2]

Intuition#

核心思路:Dutch National Flag(荷蘭國旗問題)——用三個指標將陣列分為三個區域:0 區、1 區、2 區。

  • 三個指標:lo 指向 0 區的右邊界,hi 指向 2 區的左邊界,mid 是當前掃描位置
  • 遇到 0 往前交換,遇到 2 往後交換,遇到 1 跳過

Approaches#

1: 兩次遍歷(計數排序)#

  • 概念: 第一次遍歷統計 0、1、2 的數量,第二次遍歷按順序填入
  • 時間複雜度: O(n) - 兩次遍歷
  • 空間複雜度: O(1) - 只用三個計數器
class Solution {
    fun sortColors(nums: IntArray) {
        val count = IntArray(3)
        for (num in nums) count[num]++
        var idx = 0
        for (color in 0..2) {
            repeat(count[color]) {
                nums[idx++] = color
            }
        }
    }
}

⭐2: Dutch National Flag(一次遍歷)#

  • 概念: 三指標分區——lo 追蹤 0 的右邊界,hi 追蹤 2 的左邊界,mid 掃描
  • 時間複雜度: O(n) - 一次遍歷
  • 空間複雜度: O(1) - 原地交換
class Solution {
    fun sortColors(nums: IntArray) {
        var lo = 0
        var mid = 0
        var hi = nums.lastIndex

        while (mid <= hi) {
            when (nums[mid]) {
                0 -> {
                    nums[mid] = nums[lo].also { nums[lo] = nums[mid] }
                    lo++
                    mid++
                }
                1 -> {
                    mid++
                }
                2 -> {
                    nums[mid] = nums[hi].also { nums[hi] = nums[mid] }
                    hi--
                    // mid 不動,因為交換過來的值還沒檢查
                }
            }
        }
    }
}

注意:遇到 2 交換後 mid 不能前進,因為從 hi 換過來的值還未被檢查。遇到 0 交換後 mid 可以前進,因為 lo 左邊只可能是 0 或已處理的 1。

🔑 Takeaways#

  • Pattern: Dutch National Flag 三向切分,是 Quick Sort 三向切分的基礎
  • 關鍵技巧: 關鍵在於理解何時 mid 前進、何時不動;此演算法由 Dijkstra 提出,也用於 Quick Sort 處理大量重複元素