Hard草稿★★★★★O(log n) 時間 · O(n) 空間
DesignIntervalsBinary SearchTreeMap
Patterns🏗️ 資料結構設計📊 區間合併・排程🎯 二分搜尋
尚未複習過

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

352Data Stream as Disjoint IntervalsIntervalsHardIntervals

設計一個資料結構,接收整數串流並以不相交區間的形式總結目前收到的數字。實作 addNum(int value)getIntervals() 兩個方法。

Example:

Input: addNum(1), addNum(3), addNum(7), addNum(2), addNum(6) getIntervals() -> [[1,3],[6,7]]

Intuition

TIP

核心思路:用 TreeMap 維護區間集合,每次新增數字時檢查是否能與相鄰區間合併。

Approaches

1. TreeSet + Rebuild Each Time — addNum O(log n), getIntervals O(n) / O(n)
  • Idea: 用 TreeSet 存所有數字,getIntervals 時掃描連續段
  • Time: addNum O(log n)、getIntervals O(n)
  • Space: O(n)
class SummaryRanges() {

    private val nums = TreeSet<Int>()

    fun addNum(value: Int) {
        nums.add(value)
    }

    fun getIntervals(): Array<IntArray> {
        if (nums.isEmpty()) return emptyArray()
        val result = mutableListOf<IntArray>()
        var start = -1
        var end = -1
        for (num in nums) {
            if (start == -1) {
                start = num
                end = num
            } else if (num == end + 1) {
                end = num
            } else {
                result.add(intArrayOf(start, end))
                start = num
                end = num
            }
        }
        result.add(intArrayOf(start, end))
        return result.toTypedArray()
    }
}
⭐ 2. TreeMap Maintaining Intervals — addNum O(log n), getIntervals O(n) / O(n)
  • Idea: TreeMap 的 key 是區間起點、value 是區間終點。新增數字時用 floorKey/higherKey 找相鄰區間並合併
  • Time: addNum O(log n)、getIntervals O(n)
  • Space: O(n) - n 為不相交區間數量
class SummaryRanges() {

    // key = 區間起點, value = 區間終點
    private val intervals = TreeMap<Int, Int>()

    fun addNum(value: Int) {
        // 已存在於某區間中
        val lo = intervals.floorKey(value)
        val hi = intervals.higherKey(value)

        if (lo != null && intervals[lo]!! >= value) {
            // value 已被 [lo, intervals[lo]] 覆蓋
            return
        }

        val mergeLeft = lo != null && intervals[lo]!! + 1 == value
        val mergeRight = hi != null && hi == value + 1

        if (mergeLeft && mergeRight) {
            // 橋接左右區間
            intervals[lo] = intervals[hi]!!
            intervals.remove(hi)
        } else if (mergeLeft) {
            // 擴展左區間
            intervals[lo] = value
        } else if (mergeRight) {
            // 擴展右區間(需要更新 key)
            intervals[value] = intervals[hi]!!
            intervals.remove(hi)
        } else {
            // 獨立新區間
            intervals[value] = value
        }
    }

    fun getIntervals(): Array<IntArray> {
        return intervals.entries.map { intArrayOf(it.key, it.value) }.toTypedArray()
    }
}

🔑 Takeaways