BST — 實戰題解

樹與堆積優先度 4/5 — 高價值 — 這裡有缺口就會掉關高價值 更新於 Nov 12, 2026

範圍 — 在 bst.md 某個模板上多加一步的 BST 題,每題以一張題目卡寫一次,放在它歸約到的那張卡底下;模板或進階卡已經寫出的題目,這裡只列指標、不重解。 另見:bst.md — 這些題目歸約到的 BST 模板;bst_advanced.md — 延遲式迭代器、修復壞掉的 BST、rank 查詢與各種建構變形;tree_lca_distance.md — 根到葉與任意點到任意點的路徑題(LC 112、113、124、129、257、437),用不到大小順序;binary_tree.md — LC 298 以及其他無序樹的 DFS 寫法。

LeetCode 題目清單

總覽

BST 題組,依各題歸約到的 bst.md 模板分組,順序與那份講義的模板順序相同。模板本身已經解掉的題目, 在索引裡只是一列指向它的家,不會再抄一份。原本放在這裡的路徑和家族在任何二元樹上都成立,所以它們在 tree_lca_distance.md。

索引

LC 題目 歸約到 難度
776 Split BST bst § Template 3b — 兩邊都留下,而不是丟掉一邊 Medium
938 Range Sum of BST bst § Template 3b — 把範圍內的值加總,而不是把子節點接回去 Easy
98 Validate Binary Search Tree — 在 bst § Template 4 寫出,含迭代版 bst § Template 4 Medium
449 Serialize and Deserialize BST bst § Template 4 — 由上下界決定每個前序值放哪裡 Medium
538 Convert BST to Greater Tree bst § Template 5,反向 Medium
1038 Binary Search Tree to Greater Sum Tree — 與 LC 538 同一題、同一份程式碼 bst § Template 5,反向 Medium
173 Binary Search Tree Iterator — 在 bst_advanced § Template 1 寫出 bst § Template 5,在兩次呼叫之間暫停 Medium

修剪到範圍的例題

Split BST — LC 776

關鍵 — 修剪會丟掉根在範圍外的那一側;拆分則把它留下來當第二棵樹。每個節點回傳的兩棵樹裡, 有一棵就是節點自己,另一棵只來自「可能跨過 target 的那個子節點」的拆分,所以只走一條路徑。

歸約到:bst § Template 3b —— 同樣是「另一側整個落在切點的同一邊」, 只是什麼都不丟。

python
# python
# LC 776 - Split BST
# IDEA: root <= target -> root heads the small tree; split its right child and keep the small part;
#       root > target  -> root heads the big tree; split its left child and keep the big part
# time = O(h), space = O(h)
def splitBST(root, target):
    if not root:
        return [None, None]                 # [<= target, > target]
    if root.val <= target:
        small, big = splitBST(root.right, target)
        root.right = small                  # the part of the right subtree that stays with root
        return [root, big]
    small, big = splitBST(root.left, target)
    root.left = big                         # the part of the left subtree that stays with root
    return [small, root]
java
// java
// LC 776 - Split BST
// IDEA: split only the child that straddles target, reattach the half that belongs with root
// time = O(h), space = O(h)
public TreeNode[] splitBST(TreeNode root, int target) {
    if (root == null) {
        return new TreeNode[]{null, null};
    }
    if (root.val <= target) {
        TreeNode[] split = splitBST(root.right, target);
        root.right = split[0];
        return new TreeNode[]{root, split[1]};
    }
    TreeNode[] split = splitBST(root.left, target);
    root.left = split[1];
    return new TreeNode[]{split[0], root};
}
text
[4,2,6,1,3,5,7], target = 2
at 4: 4 > 2   -> split 4.left (2); 4 keeps the big half of it
at 2: 2 <= 2  -> split 2.right (3); 2 keeps the small half of it
at 3: 3 > 2   -> split 3.left (null) -> [null, null]; return [null, 3]
back at 2: 2.right = null           -> return [2, 3]
back at 4: 4.left = 3               -> return [2, 4]
small tree [2,1]          big tree [4,3,6,null,null,5,7]

陷阱

  • 接錯一半:root.val <= target 時,root.right 要接 split[0](小的那半),不是 split[1]。 接反的話,上例中 3 會跑到 2 底下。
  • 兩個子節點都拆是 O(n),結果仍然正確;但只有一個子節點可能跨過切點。
操作 LC 移除的節點 回傳
Delete 450 一個 一個 root
Trim 669 所有在 [low, high] 之外的節點 一個 root
Split 776 沒有 兩個 root

Range Sum of BST — LC 938

關鍵 — 跟 trimBST 一樣的三路剪枝:範圍外的 root 會排除整整一邊, 所以只有合併那一步改變,從把子節點接回去,變成把值加起來。

歸約到:bst § Template 3b — 求和,而不是重組結構。

python
# python
# IDEA: Template 3b's pruning; add the in-range values instead of reattaching them
# time = O(n), space = O(h)
# LC 938 - Range Sum of BST
def rangeSumBST(root, low, high):
    if not root:
        return 0
    if root.val < low:                    # nothing on the left can be in range
        return rangeSumBST(root.right, low, high)
    if root.val > high:                   # nothing on the right can be in range
        return rangeSumBST(root.left, low, high)
    return (root.val
            + rangeSumBST(root.left, low, high)
            + rangeSumBST(root.right, low, high))

用上下界驗證的例題

Serialize and Deserialize BST — LC 449

關鍵 — BST 光靠前序就能決定整棵樹:下一個值落在目前子樹的 (low, high) 區間內, 它才屬於這棵子樹。所以編碼不需要 null 標記,這正是它比 LC 297 的編碼更精簡的原因。

歸約到:bst § Template 4 —— 同一個往下帶的區間, 拿來決定值放哪裡,而不是拿來拒絕。

python
# python
# LC 449 - Serialize and Deserialize BST
# IDEA: serialize = preorder values only; deserialize = rebuild with (low, high) bounds:
#       the next value starts this subtree iff it fits the interval, else this subtree is empty
# time = O(n), space = O(n)
from collections import deque

class Codec:
    def serialize(self, root):
        vals = []

        def preorder(node):
            if not node:
                return
            vals.append(str(node.val))
            preorder(node.left)
            preorder(node.right)

        preorder(root)
        return ",".join(vals)

    def deserialize(self, data):
        vals = deque(int(x) for x in data.split(",")) if data else deque()

        def build(low, high):
            if not vals or not (low < vals[0] < high):
                return None                    # the next value belongs to an ancestor
            node = TreeNode(vals.popleft())
            node.left = build(low, node.val)
            node.right = build(node.val, high)
            return node

        return build(float("-inf"), float("inf"))

陷阱

  • "".split(",") 是 [""],而 int("") 會丟例外:空樹要單獨擋掉。
  • 嚴格的 < 假設值都不同,BST 保證這點。若有重複值,光靠前序就無法決定形狀。
  • LC 297 那種帶 # 標記的層序編碼在這裡也會過,但它沒用到 BST,寫出的符號大約多一倍。 要能說出為什麼有了上下界就不需要標記。

相似題 — LC 1008(只做反序列化那一半,bst_advanced § Template 4) · LC 297(任意二元樹:一定要標記)

中序走訪的例題

Convert BST to Greater Tree — LC 538

關鍵 — 右 → 節點 → 左依遞減走訪各個值,所以一個累加和就能把每個節點換成 所有大於等於它的值的總和。LC 1038 是同一題。

歸約到:bst § Template 5,反過來走(Reverse Inorder for Descending)。

python
# python
# IDEA: reverse in-order (right, node, left) with a running sum of the values seen so far
# time = O(n), space = O(h)
# LC 538 - Convert BST to Greater Tree
class Solution(object):
    def convertBST(self, root):
        self.running = 0

        def reverse_inorder(node):
            if not node:
                return
            reverse_inorder(node.right)   # larger values first
            self.running += node.val
            node.val = self.running
            reverse_inorder(node.left)

        reverse_inorder(root)
        return root