樹的模式模板 — 索引與走訪變形

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

範圍 — 從每個樹的模式連到教它的那一張卡片的索引,再加上核心表單沒寫出來的幾種走訪:迭代的前序與後序、之字形層序、左下角的值,以及垂直順序。 另見:binary_tree.md — 二元樹的模板,一個一張卡片;tree.md — 概念、樹的種類、什麼時候用哪種走訪;tree_lca_distance.md — LCA、節點距離與根到葉的路徑;tree_examples.md — 實作過的題目;tree_construction.md 與 tree_codec.md — 從編碼建樹,以及把樹序列化回去;bst.md — 有序的樹。

LeetCode 題目清單

概觀

這份表單以前對每個樹的模板都有自己的一份,和 binary_tree.md、tree_lca_distance.md、 tree_examples.md 裡的版本並存,結果各份漸行漸遠。現在每個模式只有一個家,下面每一節都寫明它在哪裡。留在這裡的只有幾種沒有別處可放的寫法:用顯式堆疊的前序與後序、之字形 BFS、左下角的值,以及依座標排序的垂直順序。


辨識

這裡只列這份表單還負責的幾種寫法;其他所有樹的訊號都在 binary_tree.md 的表裡。

題目敘述裡的訊號 限制條件線索 該用 不適用時
「前序走訪」,再追問「用迭代」 n ≤ 100 1.1) 一個堆疊,先推右子節點 不用遞迴的中序 → binary_tree § Template 2
「後序走訪」、「用迭代」 n ≤ 100 1.3) 兩個堆疊,或反轉一次「根-右-左」的走訪 順序不重要、只要拜訪到 → 任何走訪都行
「之字形層序」、「左到右與右到左交替」 n ≤ 2000 1.5) 層序 BFS,每隔一層反轉 每層同一個方向 → binary_tree § Template 2
「最後一列最左邊的值」 n ≤ 10⁴ 2.7) 每層索引 0 的節點 每一層最右邊的值(LC 199)→ tree_examples.md
「垂直順序」,平手依值排 n ≤ 1000 8.3) 收集 (col, row, val) 後排序 平手保留 BFS 順序(LC 314)→ 依欄做 BFS 分組,不排序

1) 樹走訪模板

這一節以前放的 現在在哪裡
1.2) 中序模板 — LC 94 binary_tree § Template 1(遞迴)與 § Template 2(堆疊版);O(1) 空間的 Morris 走訪在 tree.md § Template 1
1.4) BFS 模板(層序)— LC 102 binary_tree § Template 2 — 佇列長度快照

1.1) 前序模板 — LC 144

模式:根 → 左 → 右 適用情境:處理子節點之前需要先拿到父節點的資料 時間複雜度:O(n) 空間複雜度:O(h),遞迴堆疊

模板程式碼

python
# Python - Recursive
def preorder_traversal(root):
    result = []

    def preorder(node):
        if not node:
            return

        # Process root first
        result.append(node.val)

        # Then left subtree
        preorder(node.left)

        # Then right subtree
        preorder(node.right)

    preorder(root)
    return result

# Python - Iterative
def preorder_iterative(root):
    if not root:
        return []

    result = []
    stack = [root]

    while stack:
        node = stack.pop()
        result.append(node.val)

        # Add right first (LIFO - will process left first)
        if node.right:
            stack.append(node.right)
        if node.left:
            stack.append(node.left)

    return result
java
// Java - Recursive
public void preorderTraversal(TreeNode root, List<Integer> result) {
    if (root == null) return;

    result.add(root.val);              // Process root
    preorderTraversal(root.left, result);   // Left subtree
    preorderTraversal(root.right, result);  // Right subtree
}

// Java - Iterative
public List<Integer> preorderIterative(TreeNode root) {
    List<Integer> result = new ArrayList<>();
    if (root == null) return result;

    Stack<TreeNode> stack = new Stack<>();
    stack.push(root);

    while (!stack.isEmpty()) {
        TreeNode node = stack.pop();
        result.add(node.val);

        if (node.right != null) stack.push(node.right);
        if (node.left != null) stack.push(node.left);
    }

    return result;
}

LeetCode 題目

  • LC 144: Binary Tree Preorder Traversal (Easy)
  • LC 589: N-ary Tree Preorder Traversal (Easy)

1.3) 後序模板 — LC 145

模式:左 → 右 → 根 適用情境:處理父節點之前需要先拿到子節點的資料 時間複雜度:O(n) 空間複雜度:O(h)

模板程式碼

python
# Python - Recursive
def postorder_traversal(root):
    result = []

    def postorder(node):
        if not node:
            return

        # Left subtree first
        postorder(node.left)

        # Right subtree
        postorder(node.right)

        # Process root last
        result.append(node.val)

    postorder(root)
    return result

# Python - Iterative (Two Stacks)
def postorder_iterative(root):
    if not root:
        return []

    stack1 = [root]
    stack2 = []

    # Collect nodes in reverse postorder
    while stack1:
        node = stack1.pop()
        stack2.append(node)

        if node.left:
            stack1.append(node.left)
        if node.right:
            stack1.append(node.right)

    # Pop from stack2 to get postorder
    result = []
    while stack2:
        result.append(stack2.pop().val)

    return result
java
// Java - Recursive
public void postorderTraversal(TreeNode root, List<Integer> result) {
    if (root == null) return;

    postorderTraversal(root.left, result);   // Left subtree
    postorderTraversal(root.right, result);  // Right subtree
    result.add(root.val);                    // Current node
}

LeetCode 題目

  • LC 145: Binary Tree Postorder Traversal (Easy)
  • LC 590: N-ary Tree Postorder Traversal (Easy)

1.5) BFS + 方向模板 — LC 103

模式:每一層交替方向 適用情境:鋸齒狀走訪 時間複雜度:O(n) 空間複雜度:O(w)

模板程式碼

python
# Python - Zigzag Level Order
from collections import deque

def zigzag_level_order(root):
    if not root:
        return []

    result = []
    queue = deque([root])
    left_to_right = True

    while queue:
        level_size = len(queue)
        current_level = deque()

        for _ in range(level_size):
            node = queue.popleft()

            # Add to level based on direction
            if left_to_right:
                current_level.append(node.val)
            else:
                current_level.appendleft(node.val)

            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)

        result.append(list(current_level))
        left_to_right = not left_to_right

    return result
java
// Java - Zigzag Level Order
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
    List<List<Integer>> result = new ArrayList<>();
    if (root == null) return result;

    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);
    boolean leftToRight = true;

    while (!queue.isEmpty()) {
        int levelSize = queue.size();
        LinkedList<Integer> currentLevel = new LinkedList<>();

        for (int i = 0; i < levelSize; i++) {
            TreeNode node = queue.poll();

            if (leftToRight) {
                currentLevel.addLast(node.val);
            } else {
                currentLevel.addFirst(node.val);
            }

            if (node.left != null) queue.offer(node.left);
            if (node.right != null) queue.offer(node.right);
        }

        result.add(currentLevel);
        leftToRight = !leftToRight;
    }

    return result;
}

LeetCode 題目

  • LC 103: Binary Tree Zigzag Level Order Traversal (Medium)

2) 樹的性質模板

這一節以前放的 現在在哪裡
2.1) 後序高度模板 — LC 104 Maximum Depth of Binary Tree — LC 104
2.2) BFS 提早停止模板 — LC 111 Minimum Depth of Binary Tree — LC 111 — BFS 彈出的第一個葉子
2.3) 高度驗證模板 — LC 110 Balanced Binary Tree — LC 110 — 回傳 -1 當作不平衡的訊號
2.4) 鏡像驗證模板 — LC 101 Symmetric Tree — LC 101
2.5) 比較兩棵樹的模板 — LC 100 binary_tree § Template 12 — 兩棵樹同步走
2.6) 最小深度 — 遞迴寫法與單一子節點的陷阱 同一張 LC 111 卡片,它的陷阱有講只有一個子節點的情況

2.7) 最大深度上最左邊的值 — LC 513

關鍵 — 層序 BFS 由上往下走過每一層、每層由左往右,所以每層第一個彈出的節點就是該層最左邊的;照這樣最後記下的值就是左下角那個。

歸約到:binary_tree § Template 2 — 佇列長度快照,只記每層索引 0 的節點,而不是整層。

python
# python
# LC 513 - Find Bottom Left Tree Value
# IDEA: level-order BFS; the first node of each level overwrites the answer, so the last level wins
# time = O(n), space = O(w)   w = maximum width of a level
from collections import deque

def find_bottom_left_value(root):
    queue = deque([root])
    leftmost = root.val
    while queue:
        level_size = len(queue)
        for i in range(level_size):
            node = queue.popleft()
            if i == 0:                     # first node of this level
                leftmost = node.val
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
    return leftmost
java
// java
// LC 513 - Find Bottom Left Tree Value
// IDEA: level-order BFS; record index 0 of every level
// time = O(n), space = O(w)
public int findBottomLeftValue(TreeNode root) {
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);
    int leftmost = root.val;
    while (!queue.isEmpty()) {
        int levelSize = queue.size();
        for (int i = 0; i < levelSize; i++) {
            TreeNode node = queue.poll();
            if (i == 0) {
                leftmost = node.val;
            }
            if (node.left != null) {
                queue.offer(node.left);
            }
            if (node.right != null) {
                queue.offer(node.right);
            }
        }
    }
    return leftmost;
}

陷阱

  • 「最左」是對每一層而言,不是「一直往左走」:在 [1,2,3,4,null,5,6,null,null,7] 裡,答案 7 在右子節點底下。
  • 先放右再放左,最後彈出的節點就是左下角那個:只要一個變數的版本,但整個技巧就在順序上,所以要說出來。

3) 路徑類模板

每個路徑模板都搬到負責路徑的那份表單,或搬到轉彎路徑題所歸約到的高度卡片:

這一節以前放的 現在在哪裡
3.1) 全域最大值更新模板 — LC 124 Binary Tree Maximum Path Sum — LC 124
3.2) 路徑累加模板 — LC 112 Path Sum — LC 112
3.3) 路徑+回溯模板 — LC 113 tree_lca_distance § Template 4
3.4) 路徑計數模板 — LC 437 tree_lca_distance § Template 5
3.5) 路徑數值建構模板 — LC 129 Sum Root to Leaf Numbers — LC 129
3.6) 路徑狀態追蹤模板 — LC 1448 Count Good Nodes in Binary Tree — LC 1448
3.7) 最長路徑模板 — LC 543 binary_tree § Template 5
3.8) 同值路徑模板 — LC 687 Longest Univalue Path — LC 687

4) 距離與 LCA 模板

這四份模板已經搬出這份表單。現在由 tree_lca_distance.md 負責,而且每一份都講得比這裡能容納的篇幅長上好幾倍:

這一節原本放什麼 現在在哪裡
4.1) LCA 標準模板 — LC 236 LCA — LC 236,還多了 LC 865 / 1123 的最深節點變形
4.2) 值比較模板 — LC 235 同一節 — LC 235 是同一份模板在 BST 上的捷徑
4.3) 路徑距離模板 — LC 1740 Distance Between Nodes — LC 1740
4.4) 樹轉圖模板 — LC 863 Move Parent Pattern,那是一般化的形式 — LC 863 和 LC 742 都是它的實例

從這裡該帶走的想法:樹上每一個「距離」問題,骨子裡都是 LCA 問題,因為兩個節點之間 唯一的路徑必定經過它們的最低共同祖先 — dist(p, q) = depth(p) + depth(q) - 2·depth(lca)。 如果還需要往上走,就加上父指標,把樹當成無向圖來處理。

6) 樹的建構模板

建構也搬出這份表單了;由兩份 Tier 1 表單分別接手:

這一節原本放什麼 現在在哪裡
6.1) 建樹模板 — LC 105 / 106 tree_construction.md — 這份表單原本的 Java 模板和它的 LC 106 後序變形都併過去了
6.2) 字串轉換模板 — LC 297 tree_codec.md — 整個 codec 家族,LC 297 / 449 / 331
6.3) 字串建構模板 — LC 606 tree_codec.md — 括號格式與省略成對括號的規則

從這裡該帶走的想法:每一個建構問題都是同一套遞迴 — 從編碼中認出根節點、算出輸入中 有多少屬於各棵子樹,然後遞迴下去。只有第一步不一樣:前序的開頭、後序的結尾、某個區間的 最大值,或是第一個 ( 之前的 token。

7) 改樹模板

這一節以前放的 現在在哪裡
7.1) 翻轉樹模板 — LC 226 Invert Binary Tree — LC 226
7.2) 把樹攤平的模板 — LC 114 Flatten Binary Tree to Linked List — LC 114
7.3) 合併樹的模板 — LC 617 Merge Two Binary Trees — LC 617
刪除節點並回傳森林 — LC 1110 binary_tree § Template 10

8) 進階樹模板

這一節以前放的 現在在哪裡
8.1) O(1) 空間的層連結模板 — LC 117 binary_tree § Template 8
8.2) 後序樹 DP(回傳一對值)模板 — LC 337 binary_tree § Template 9
8.4) 完全樹節點計數模板 — LC 222 binary_tree § Template 7

8.3) 座標對應走訪模板 — LC 987

關鍵 — 給每個節點一個座標(left → col - 1、right → col + 1、深度 → row),輸出順序就是單純依 (col, row, val) 排序。只用 BFS 不夠:兩個節點可能同一個 (col, row),平手時要依值排,所以先收集、再排序。

歸約到:binary_tree § Template 1 — 任何把 (row, col) 往下帶的走訪,最後排序一次。

python
# python
# LC 987 - Vertical Order Traversal of a Binary Tree
# IDEA: DFS collecting (col, row, val); plain tuple sort gives col → row → val
# time = O(N log N), space = O(N)
def vertical_traversal(root):
    nodes = []

    def dfs(node, row, col):
        if not node:
            return
        nodes.append((col, row, node.val))
        dfs(node.left,  row + 1, col - 1)
        dfs(node.right, row + 1, col + 1)

    dfs(root, 0, 0)
    nodes.sort()

    res, prev_col = [], None
    for col, row, val in nodes:
        if col != prev_col:
            res.append([])
            prev_col = col
        res[-1].append(val)
    return res
java
// java
// LC 987 - Vertical Order Traversal of a Binary Tree
// IDEA: DFS collecting (col, row, val) triples, then sort col → row → val
// time = O(N log N), space = O(N)
public List<List<Integer>> verticalTraversal(TreeNode root) {
    List<int[]> nodes = new ArrayList<>();   // {col, row, val}
    dfs(root, 0, 0, nodes);

    nodes.sort((a, b) -> a[0] != b[0] ? Integer.compare(a[0], b[0])
                       : a[1] != b[1] ? Integer.compare(a[1], b[1])
                       : Integer.compare(a[2], b[2]));

    List<List<Integer>> res = new ArrayList<>();
    Integer prevCol = null;
    for (int[] n : nodes) {
        if (prevCol == null || n[0] != prevCol) {
            res.add(new ArrayList<>());
            prevCol = n[0];
        }
        res.get(res.size() - 1).add(n[2]);
    }
    return res;
}

private void dfs(TreeNode node, int row, int col, List<int[]> nodes) {
    if (node == null) return;
    nodes.add(new int[]{col, row, node.val});
    dfs(node.left,  row + 1, col - 1, nodes);
    dfs(node.right, row + 1, col + 1, nodes);
}

陷阱

  • 用 BFS 依欄分組卻不排序:LC 987 範例 3 的 5 和 6 在同一個 (col, row),必須輸出成 [1,5,6],依值排序,而不是依拜訪順序。
  • LC 314(垂直順序,沒有依值的平手規則)要的是欄內依 BFS 順序,所以那裡的排序鍵是 (col, BFS index),不是 (col, row, val)。

練習路徑

階段 題目(易 → 難) 多學到什麼
核心 LC 144 未評定 · LC 145 未評定 · LC 103 未評定 兩種顯式堆疊的走訪,再來是每層方向互換的層序 BFS
練習 LC 513 未評定 · LC 589 未評定 · LC 590 未評定 最後一層的第一個節點;同樣的堆疊走訪用在 N 元樹上
延伸 LC 987 未評定 · LC 314 未評定 座標,平手先依值、再依 BFS 順序