#105 树分治 中等

从前序与中序遍历序列构造二叉树

在 LeetCode 上查看 ↗

🎧 语音讲解

开车或通勤时可听,跟着思路走一遍

速度

📋 题目描述

给定两个整数数组 preorder 和 inorder,其中 preorder 是二叉树的先序遍历,inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

题目保证 preorder 与 inorder 元素无重复,且确实对应一棵二叉树的前序与中序序列;长度相等且在 [1, 3000] 内。

示例 1

输入:preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出:[3,9,20,null,null,15,7]
先序首元素 3 为根;中序里 3 左侧 [9] 为左子树、右侧 [15,20,7] 为右子树,再递归构造。

示例 2

输入:preorder = [-1], inorder = [-1]
输出:[-1]

💭 模拟答题者思考

1. 暴力直觉:枚举所有二叉树形状,对每个树检查先序、中序是否匹配——指数级,不可行。

2. 先序的第一个数一定是当前子树的根;中序里根左边的连续段是左子树的中序,右边是右子树的中序——子问题与整题同型,天然分治。

3. 若每次在中序里线性找根位置并切片 preorder/inorder,最坏 O(n²);用哈希表记下每个值在中序的下标,配合全局 pre_i 按先序依次「消费」节点,每个值只处理一次 → O(n)。

4. 示例 1:根 3,中序 mid=1,左区间 [0,0] 得到节点 9,右区间 [2,4] 先建 20 再 15、7,与先序 [20,15,7] 一致。

5. 递归深度 O(h),表 O(n);必须先递归左子树再右子树,否则 pre_i 与先序不对齐。

🧠 变量语义(先读这三句再编码)

变量类型语义(三句法)
preorder[pre_i]int / 游标定义:全局先序指针,下一个要「落地」为树节点的值
维护:每创建一个节点后 pre_i += 1,顺序与先序遍历一致
更新:递归先建左再建右,指针自然扫完整棵树的先序
in_left, in_rightint定义:当前子树在 inorder 中的闭区间下标
维护:in_left > in_right 表示空子树,返回 null
更新:根在中序位置 mid 处,左子树 [in_left, mid-1],右子树 [mid+1, in_right]
idx_map[val]哈希表定义:中序每个值 → 下标,O(1) 找根在中序中的位置
维护:构造前一次性建好,全程不变
更新:读到根值 root_val 后 mid = idx_map[root_val] 用于切分左右区间
rootTreeNode*定义:当前子树的根,值来自当前 preorder[pre_i]
维护:先挂好 left、right 再返回给上层
更新:root.left = dfs(...) 再 root.right = dfs(...)(顺序必须与先序一致)

⌨️ 落码步骤

1. 建 idx_map:inorder[i] → i

2. 定义 dfs(in_left, in_right):若 in_left > in_right 返回 null

3. 取 root_val = preorder[pre_i++],mid = idx_map[root_val],新建根节点

4. root.left = dfs(in_left, mid - 1),再 root.right = dfs(mid + 1, in_right)

5. 从 dfs(0, len(inorder)-1) 返回整棵树的根

💻 代码实现

class Solution:
    def buildTree(self, preorder: list[int], inorder: list[int]) -> TreeNode | None:
        idx_map = {v: i for i, v in enumerate(inorder)}
        pre_i = 0

        def dfs(in_left: int, in_right: int) -> TreeNode | None:
            nonlocal pre_i
            if in_left > in_right:
                return None
            root_val = preorder[pre_i]
            pre_i += 1
            mid = idx_map[root_val]
            node = TreeNode(root_val)
            node.left = dfs(in_left, mid - 1)
            node.right = dfs(mid + 1, in_right)
            return node

        return dfs(0, len(inorder) - 1)
class Solution {
    int pre_i = 0;
    unordered_map<int, int> idx_map;

    TreeNode* dfs(int in_left, int in_right, vector<int>& preorder) {
        if (in_left > in_right) return nullptr;
        int root_val = preorder[pre_i++];
        int mid = idx_map[root_val];
        auto* root = new TreeNode(root_val);
        root->left = dfs(in_left, mid - 1, preorder);
        root->right = dfs(mid + 1, in_right, preorder);
        return root;
    }

public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        pre_i = 0;
        idx_map.clear();
        for (int i = 0; i < (int)inorder.size(); ++i)
            idx_map[inorder[i]] = i;
        return dfs(0, (int)inorder.size() - 1, preorder);
    }
};
// 时间 O(n),空间 O(n)

📈 复杂度分析

时间复杂度 O(n)
空间复杂度 O(n)(中序下标哈希表 + 递归栈 O(h))

⚠️ 常见坑

❗ 先递归右子树再左子树:pre_i 按先序推进,顺序错了会挂错节点。

❗ 右子树中序区间写成 [mid, in_right]:根已在 mid,右子树必须从 mid + 1 开始。

❗ 用切片复制子数组:正确但 Python 切片会多 O(n²) 拷贝;应像上面用下标区间 + 全局先序指针。

🔍 必测边界 Case

Case 1:单节点
preorder = [-1], inorder = [-1] → 根 -1,无孩子
Case 2:只有左链
pre = [1,2,3], in = [3,2,1] → 1 的左子树为空、右子树为 2→3
Case 3:只有右链
pre = [1,2,3], in = [1,2,3] → 每层只有右孩子
Case 4:官方示例 1
preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Case 5:左子树为空区间
根在中序最左端 → dfs(in_left, mid-1) 立即返回 null