面试经典150题[072]:从前序与中序遍历序列构造二叉树(LeetCode 105)

从前序与中序遍历序列构造二叉树(LeetCode 105)

题目链接:从前序与中序遍历序列构造二叉树(LeetCode 105)

难度:中等

1. 题目描述

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

要求:

  • 树中不存在重复元素
  • 数组长度 1 <= n <= 3000
  • -3000 <= preorder[i], inorder[i] <= 3000

示例:

复制代码
输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]

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

2. 问题分析

2.1 规律

  • 先序遍历:根节点 -> 左子树 -> 右子树
  • 中序遍历:左子树 -> 根节点 -> 右子树
  • 先序的第一个元素总是根节点,在中序中找到根节点的位置,可以分割左/右子树。
  • 递归构建:左子树的前序/中序子序列,右子树的前序/中序子序列。
  • 核心问题:如何高效找到中序中根节点的位置,以分割子树?

2.2 贪心思路

我们使用递归 + 哈希表优化:

  • 使用哈希表存储中序遍历的值到索引的映射,便于 O(1) 查找根节点位置。
  • 递归函数:参数为前序/中序的起始/结束索引。
  • 步骤:
    • 如果子树为空(start > end),返回 None。
    • 取前序第一个元素作为根,pre_idx += 1。
    • 在中序中找到根索引 root_in_idx。
    • 递归构建左子树:中序 [in_start, root_in_idx-1],前序相应部分。
    • 递归构建右子树:中序 [root_in_idx+1, in_end],前序相应部分。
  • 全局 pre_idx 跟踪前序位置,避免切片开销。

3. 代码实现

Python

python 复制代码
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
        def helper(in_start, in_end):
            nonlocal pre_idx
            if in_start > in_end:
                return None
            
            root_val = preorder[pre_idx]
            root = TreeNode(root_val)
            pre_idx += 1
            
            root_in_idx = idx_map[root_val]
            
            root.left = helper(in_start, root_in_idx - 1)
            root.right = helper(root_in_idx + 1, in_end)
            
            return root
        
        n = len(preorder)
        idx_map = {val: i for i, val in enumerate(inorder)}
        pre_idx = 0
        return helper(0, n - 1)

C++

cpp 复制代码
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        unordered_map<int, int> idx_map;
        for (int i = 0; i < inorder.size(); ++i) {
            idx_map[inorder[i]] = i;
        }
        int pre_idx = 0;
        return helper(preorder, inorder, idx_map, pre_idx, 0, inorder.size() - 1);
    }
    
private:
    TreeNode* helper(vector<int>& preorder, vector<int>& inorder, unordered_map<int, int>& idx_map, int& pre_idx, int in_start, int in_end) {
        if (in_start > in_end) {
            return nullptr;
        }
        
        int root_val = preorder[pre_idx];
        TreeNode* root = new TreeNode(root_val);
        ++pre_idx;
        
        int root_in_idx = idx_map[root_val];
        
        root->left = helper(preorder, inorder, idx_map, pre_idx, in_start, root_in_idx - 1);
        root->right = helper(preorder, inorder, idx_map, pre_idx, root_in_idx + 1, in_end);
        
        return root;
    }
};

4. 复杂度分析

  • 时间复杂度:O(n),哈希表构建 O(n),递归遍历每个节点一次
  • 空间复杂度:O(n),哈希表 O(n),递归栈最坏 O(n)

5. 总结

  • 树重建问题 + 遍历序列 → 递归分割是首选
  • 核心使用哈希表优化索引查找,很通用
    • 类似 BFS 的序列化,但这里是反序列化
    • 可扩展到后序 + 中序的变体

复习

面试经典150题[012]:O(1) 时间插入、删除和获取随机元素(LeetCode 380)

面试经典150题[042]:有效的字母异位词(LeetCode 242)

面试经典150题[057]:链表中是否有环(LeetCode 141)

相关推荐
Lee川12 小时前
优雅进化的JavaScript:从ES6+新特性看现代前端开发范式
javascript·面试
Lee川15 小时前
从异步迷雾到优雅流程:JavaScript异步编程与内存管理的现代化之旅
javascript·面试
晴殇i17 小时前
揭秘JavaScript中那些“不冒泡”的DOM事件
前端·javascript·面试
绝无仅有18 小时前
Redis过期删除与内存淘汰策略详解
后端·面试·架构
绝无仅有18 小时前
Redis大Key问题排查与解决方案全解析
后端·面试·架构
AAA梅狸猫19 小时前
Looper.loop() 循环机制
面试
AAA梅狸猫19 小时前
Handler基本概念
面试
Wect19 小时前
浏览器缓存机制
前端·面试·浏览器
掘金安东尼20 小时前
Fun with TypeScript Generics:玩转 TS 泛型
前端·javascript·面试
掘金安东尼20 小时前
Next.js 企业级落地
前端·javascript·面试