Description

實作一個 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#

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

  • BST 中序遍歷產生遞增序列
  • 迭代式中序遍歷用堆疊模擬,可以隨時暫停
  • 初始化時將所有左子節點推入堆疊,next() 時彈出並轉向右子樹

Approaches#

1: 預先收集所有元素#

  • 概念: 初始化時完整中序遍歷,結果存入列表,用指標遍歷
  • 時間複雜度: next() O(1),初始化 O(n)
  • 空間複雜度: 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: 受控式中序遍歷(堆疊)#

  • 概念: 用堆疊維護從當前節點到最左的路徑,next() 時彈出最小值並處理右子樹
  • 時間複雜度: next() 平均 O(1)(攤提分析),hasNext() O(1)
  • 空間複雜度: 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
        }
    }
}
為什麼 next() 的平均時間是 O(1)?

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

🔑 Takeaways#

  • Pattern: 用堆疊模擬中序遍歷的「暫停與恢復」,是 BST 迭代器的經典實作
  • 關鍵技巧: pushAllLeft 函數是核心——確保堆疊頂端永遠是當前最小值,空間只需 O(h)