Hard草稿★★★★★O(log n) 時間 · O(n) 空間
Patterns⛰️ 堆・Top-K
尚未複習過

解法已隱藏 — 先讀題目敘述、自己想想看,再點上方按鈕揭曉。

295Find Median from Data StreamHeap / Priority QueueHardHeap / Priority Queue

設計一個資料結構,支援從資料流中新增整數,並能隨時找到目前所有已加入整數的中位數。若元素個數為偶數,中位數為中間兩數的平均值。

Example:

Input: [“MedianFinder”, “addNum”, “addNum”, “findMedian”, “addNum”, “findMedian”] [[], [1], [2], [], [3], []] Output: [null, null, null, 1.5, null, 2.0]

Intuition

TIP

核心思路:用兩個 Heap(Max Heap 存較小半邊,Min Heap 存較大半邊),中位數就在兩個堆頂之間。

Approaches

1. Sorted List (insertion sort) — O(n) / O(n)
  • Idea: 維護一個有序列表,每次用二分搜尋找到插入位置,中位數直接取中間元素
  • Time: O(n) addNum(插入需要移動元素),O(1) findMedian
  • Space: O(n)
class MedianFinder() {
    private val sorted = mutableListOf<Int>()

    fun addNum(num: Int) {
        var pos = sorted.binarySearch(num)
        if (pos < 0) pos = -(pos + 1)
        sorted.add(pos, num)
    }

    fun findMedian(): Double {
        val n = sorted.size
        return if (n % 2 == 1) {
            sorted[n / 2].toDouble()
        } else {
            (sorted[n / 2 - 1] + sorted[n / 2]) / 2.0
        }
    }
}
⭐ 2. Two Heaps — O(log n) / O(n)
  • Idea: Max Heap 存較小半邊,Min Heap 存較大半邊。新數先加入 Max Heap,再平衡兩邊大小。中位數從堆頂取得
  • Time: O(log n) addNum,O(1) findMedian
  • Space: O(n)
class MedianFinder() {
    private val small = PriorityQueue<Int>(compareByDescending { it }) // Max Heap (左半邊)
    private val large = PriorityQueue<Int>()                           // Min Heap (右半邊)

    fun addNum(num: Int) {
        small.offer(num)
        // 確保 small 的最大值 <= large 的最小值
        if (large.isNotEmpty() && small.peek() > large.peek()) {
            large.offer(small.poll())
        }
        // 平衡大小:small 最多比 large 多 1 個
        if (small.size > large.size + 1) {
            large.offer(small.poll())
        } else if (large.size > small.size) {
            small.offer(large.poll())
        }
    }

    fun findMedian(): Double {
        return if (small.size > large.size) {
            small.peek().toDouble()
        } else {
            (small.peek() + large.peek()) / 2.0
        }
    }
}

WARNING

平衡邏輯的順序很重要:先確保值的正確性(小堆的最大 <= 大堆的最小),再平衡大小。順序反過來可能導致錯誤。

🔑 Takeaways