树与递归模板:递归三要素、遍历套路、BST 操作、LCA 与回溯

二叉树与递归的模板化总结:递归三要素、前/中/后序思想、自顶向下与自底向上的套路、BST 查找插入删除、LCA 模板、回溯排列组合模板,配经典题与复杂度。

树与递归模板

树的遍历与 BST 基础操作见 二叉树与平衡树。本文聚焦递归套路的模板化:三要素、前/中/后序的思考方式、LCA 与回溯模板——树题 80% 是递归题。

一、递归三要素

写任何树的递归前,先回答三个问题:

  1. 终止条件:空节点(null)返回什么?
  2. 子问题:对左子树、右子树分别递归能得到什么?
  3. 合并:当前节点如何把左右子结果合并成自己的返回值?
def solve(root):
    if not root:          # 1. 终止条件
        return base       # 空节点返回值
    left = solve(root.left)
    right = solve(root.right)
    return merge(root.val, left, right)   # 3. 合并

做题顺序:先写终止条件,再写合并逻辑,最后检查返回值类型是否一致。

二、遍历模板

递归遍历(前/中/后序)

def preorder(root):                       # 前序:根-左-右
    if not root:
        return []
    return [root.val] + preorder(root.left) + preorder(root.right)

def inorder(root):                        # 中序:左-根-右
    if not root:
        return []
    return inorder(root.left) + [root.val] + inorder(root.right)

def postorder(root):                      # 后序:左-右-根
    if not root:
        return []
    return postorder(root.left) + postorder(root.right) + [root.val]

三种遍历的思考角度(面试常考):

遍历核心用途典型题
前序自顶向下传参数、复制树、序列化LC 297 序列化
中序BST 有序序列、验证 BSTLC 98、LC 230
后序自底向上收集信息(深度/最大路径)LC 104、LC 124、LC 543

层序遍历(BFS)

from collections import deque

def level_order(root):
    if not root:
        return []
    result, queue = [], deque([root])
    while queue:
        level = []
        for _ in range(len(queue)):       # 按层取完
            node = queue.popleft()
            level.append(node.val)
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
        result.append(level)
    return result

三、递归套路分类

套路 1:自顶向下(传参,答案在过程中记录)

适用于「从根到叶子」的路径类问题——每个节点拿到父节点传来的信息。

def has_path_sum(root, target_sum):
    # LC 112:从根到叶子是否存在路径和 = target
    if not root:
        return False
    if not root.left and not root.right:
        return root.val == target_sum
    return (has_path_sum(root.left, target_sum - root.val) or
            has_path_sum(root.right, target_sum - root.val))

套路 2:自底向上(收信息,答案在返回值/闭包中)

适用于「子树统计」类问题——每个节点汇总子树信息再上传。

def max_depth(root):
    # LC 104:最大深度
    if not root:
        return 0
    return 1 + max(max_depth(root.left), max_depth(root.right))
def max_path_sum(root):
    # LC 124:任意节点间最大路径和(后序 + 闭包)
    ans = float('-inf')
    def dfs(node):
        nonlocal ans
        if not node:
            return 0
        left = max(dfs(node.left), 0)     # 负贡献不取
        right = max(dfs(node.right), 0)
        ans = max(ans, node.val + left + right)   # 经过该节点的路径
        return node.val + max(left, right)        # 只能向单边走
    dfs(root)
    return ans

判断口诀:「要往下的信息」→ 自顶向下;「要往上的信息」→ 自底向上。

四、BST 操作模板

BST 性质:左 < 根 < 右,中序遍历即有序序列。

查找 / 插入 / 删除(LC 450)

class BST:
    def search(self, root, val):
        if not root or root.val == val:
            return root
        if val < root.val:
            return self.search(root.left, val)
        return self.search(root.right, val)

    def insert(self, root, val):
        if not root:
            return TreeNode(val)
        if val < root.val:
            root.left = self.insert(root.left, val)
        else:
            root.right = self.insert(root.right, val)
        return root

    def delete(self, root, val):
        if not root:
            return None
        if val < root.val:
            root.left = self.delete(root.left, val)
        elif val > root.val:
            root.right = self.delete(root.right, val)
        else:
            if not root.left:
                return root.right
            if not root.right:
                return root.left
            # 两个子节点:用右子树最小值替换
            min_node = self.find_min(root.right)
            root.val = min_node.val
            root.right = self.delete(root.right, min_node.val)
        return root

    def find_min(self, root):
        while root.left:
            root = root.left
        return root

验证 BST(LC 98,中序或区间约束)

def is_valid_bst(root):
    prev = [float('-inf')]
    def inorder(node):
        if not node:
            return True
        if not inorder(node.left):
            return False
        if node.val <= prev[0]:
            return False
        prev[0] = node.val
        return inorder(node.right)
    return inorder(root)

BST 题三板斧:中序遍历看有序、递归区间约束 (low, high)、把 BST 变有序数组再处理(LC 230 第 K 小)。

五、LCA 模板

最近公共祖先(LC 236):两个节点在树上的最低公共祖先。

递归模板(自底向上收集 p/q 是否在子树)

def lowest_common_ancestor(root, p, q):
    if not root or root == p or root == q:
        return root
    left = lowest_common_ancestor(root.left, p, q)
    right = lowest_common_ancestor(root.right, p, q)
    if left and right:
        return root          # 左右各含一个,当前即 LCA
    return left or right

复杂度:O(n) 时间,O(h) 空间。

变体:

  • BST 版(LC 235):利用大小关系剪枝,比普通版更快。
  • 多次查询:先预处理深度 + 倍增(up[node][k]),单次查询 O(log n)。

六、回溯模板(递归的另一种形态)

回溯 = 递归 + 撤销选择,用于排列/组合/子集(LC 46/78/90)。

def backtrack(nums):
    result, path = [], []

    def dfs(start):
        result.append(path[:])            # 子集:每个前缀都是一个答案
        for i in range(start, len(nums)):
            path.append(nums[i])
            dfs(i + 1)                    # 组合/子集:i+1 不回头
            path.pop()                    # 撤销选择
    dfs(0)
    return result
def permute(nums):
    # 全排列(LC 46):每个位置选一个未用过的数
    result, path = [], []
    used = [False] * len(nums)

    def dfs():
        if len(path) == len(nums):
            result.append(path[:])
            return
        for i in range(len(nums)):
            if used[i]:
                continue
            used[i] = True
            path.append(nums[i])
            dfs()
            path.pop()
            used[i] = False
    dfs()
    return result

回溯三步:选(append)→ 递归 → 撤销(pop)。去重时先排序,再跳过与前一元素相同且未使用的分支(LC 90/47)。

七、经典题速查表

题号题目套路难度
LeetCode 104二叉树的最大深度自底向上Easy
LeetCode 112路径总和自顶向下传参Easy
LeetCode 98验证二叉搜索树中序/区间约束Medium
LeetCode 230BST 第 K 小元素中序Medium
LeetCode 236二叉树的最近公共祖先后序递归Medium
LeetCode 124二叉树中的最大路径和后序 + 闭包Hard
LeetCode 543二叉树的直径后序 + 闭包Easy
LeetCode 297二叉树的序列化前序/层序Hard
LeetCode 46/78全排列 / 子集回溯Medium

八、常见问题

Q: 递归的空间复杂度?
递归栈深度 = 树高 h,最坏 O(n)(链状),平衡树 O(log n)。面试被问复杂度要主动提递归栈。

Q: 递归改成迭代怎么写?
前序用栈;中序用「先压左再弹中再转右」;后序用双栈/标记法;层序用队列。面试先用递归保正确,有余力再补迭代。

Q: 回溯和 DFS 的区别?
回溯是 DFS 的一种——它多了一个「撤销选择」,用于枚举所有解(排列/组合),而不是只找一条路径。


相关文章:

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页