Medium草稿★★★★O(1) 時間 · O(h) 空間
TreeBSTStackDesignInorder
Patterns🏗️ 資料結構設計🌳 樹形 DFS🥞 堆疊
尚未複習過

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

173Binary Search Tree IteratorTreesMediumTrees

實作一個 BST 的迭代器類別,初始化時指向 BST 中最小的元素之前。next() 回傳下一個最小的數,hasNext() 回傳是否還有下一個數。要求平均 O(1) 時間、O(h) 空間。

Example:

Input: [“BSTIterator”,“next”,“next”,“hasNext”,“next”,“hasNext”,“next”,“hasNext”,“next”,“hasNext”] > [[[7,3,15,null,null,9,20]],[],[],[],[],[],[],[],[],[]] Output: [null,3,7,true,9,true,15,true,20,false]

Intuition

TIP

用堆疊模擬中序遍歷的暫停與恢復,每次 next() 推進一步。

Approaches

1. Pre-collect All Elements — Init O(n), next O(1) / O(n)
  • Idea: 初始化時完整中序遍歷,結果存入列表,用指標遍歷
  • Time: next() O(1),初始化 O(n)
  • Space: O(n)
class BSTIterator(root: TreeNode?) {
    private val list = mutableListOf<Int>()
    private var index = 0

    init {
        inorder(root)
    }

    private fun inorder(node: TreeNode?) {
        if (node == null) return
        inorder(node.left)
        list.add(node.`val`)
        inorder(node.right)
    }

    fun next(): Int = list[index++]

    fun hasNext(): Boolean = index < list.size
}
⭐ 2. Controlled Inorder Traversal (stack) — next amortized O(1) / O(h)
  • Idea: 用堆疊維護從當前節點到最左的路徑,next() 時彈出最小值並處理右子樹
  • Time: next() 平均 O(1)(攤提分析),hasNext() O(1)
  • Space: O(h),h 為樹高
class BSTIterator(root: TreeNode?) {
    private val stack = ArrayDeque<TreeNode>()

    init {
        pushAllLeft(root)
    }

    fun next(): Int {
        val node = stack.removeLast()
        pushAllLeft(node.right)
        return node.`val`
    }

    fun hasNext(): Boolean = stack.isNotEmpty()

    private fun pushAllLeft(node: TreeNode?) {
        var curr = node
        while (curr != null) {
            stack.addLast(curr)
            curr = curr.left
        }
    }
}
Why next() is amortized O(1)?

雖然單次 next() 可能需要 pushAllLeft 走過多個節點(最壞 O(h)),但每個節點在整個遍歷過程中只被推入和彈出堆疊各一次。n 次 next() 總共的操作是 O(n),所以平均每次是 O(1)。這是典型的攤提分析。

🔑 Takeaways