Description

給定兩個已排序的整數陣列 nums1nums2,找出這兩個陣列合併後的中位數。要求時間複雜度為 O(log(m + n))

Example:

Input: nums1 = [1,3], nums2 = [2] Output: 2.0

Intuition#

在較短的陣列上二分搜尋「切割位置」,使得左半部的所有元素都小於等於右半部的所有元素。

  • 中位數把合併後的陣列分成等長的左右兩半
  • 我們不需要真的合併,只需要找到正確的「分割線」
  • 在較短的陣列上二分搜尋分割位置,另一個陣列的分割位置可以直接算出來
  • 驗證條件:左半部的最大值 <= 右半部的最小值

Approaches#

1: Merge and Find#

  • 概念: 合併兩個陣列後直接取中位數
  • 時間複雜度: O(m + n)
  • 空間複雜度: O(m + n)
Merge and Find 程式碼
class Solution {
    fun findMedianSortedArrays(nums1: IntArray, nums2: IntArray): Double {
        val merged = IntArray(nums1.size + nums2.size)
        var i = 0; var j = 0; var k = 0
        while (i < nums1.size && j < nums2.size) {
            if (nums1[i] <= nums2[j]) merged[k++] = nums1[i++]
            else merged[k++] = nums2[j++]
        }
        while (i < nums1.size) merged[k++] = nums1[i++]
        while (j < nums2.size) merged[k++] = nums2[j++]
        val mid = merged.size / 2
        return if (merged.size % 2 == 0) {
            (merged[mid - 1] + merged[mid]) / 2.0
        } else {
            merged[mid].toDouble()
        }
    }
}

⭐2: Binary Search on Partition#

  • 概念: 在較短陣列上二分搜尋分割位置,確保左半部最大值不超過右半部最小值
  • 時間複雜度: O(log(min(m, n)))
  • 空間複雜度: O(1)
class Solution {
    fun findMedianSortedArrays(nums1: IntArray, nums2: IntArray): Double {
        // 確保在較短的陣列上二分搜尋
        val a = if (nums1.size <= nums2.size) nums1 else nums2
        val b = if (nums1.size <= nums2.size) nums2 else nums1
        val m = a.size
        val n = b.size
        val half = (m + n + 1) / 2

        var left = 0
        var right = m
        while (left <= right) {
            val i = left + (right - left) / 2  // a 的分割位置
            val j = half - i                     // b 的分割位置

            val aLeft = if (i > 0) a[i - 1] else Int.MIN_VALUE
            val aRight = if (i < m) a[i] else Int.MAX_VALUE
            val bLeft = if (j > 0) b[j - 1] else Int.MIN_VALUE
            val bRight = if (j < n) b[j] else Int.MAX_VALUE

            if (aLeft <= bRight && bLeft <= aRight) {
                // 找到正確的分割
                return if ((m + n) % 2 == 0) {
                    (maxOf(aLeft, bLeft) + minOf(aRight, bRight)) / 2.0
                } else {
                    maxOf(aLeft, bLeft).toDouble()
                }
            } else if (aLeft > bRight) {
                right = i - 1
            } else {
                left = i + 1
            }
        }
        return 0.0
    }
}

邊界處理是這題的難點:當分割位置在陣列的最左或最右時,需要用 Int.MIN_VALUE / Int.MAX_VALUE 作為虛擬的邊界值。另外要確保始終在較短的陣列上二分,避免 j 出現負數。

🔑 Takeaways#

  • Pattern: 二分搜尋分割位置
  • 關鍵技巧: 這題的核心觀念是「中位數 = 找到正確的分割線使左右兩半等長且左半部 <= 右半部」。這個分割線的思維方式也可以用在其他「找第 k 大」的問題上