☰
LeetCode 105:从前序与中序遍历序列构造二叉树
2026/10/26 17:10:00 网站建设 项目流程

1. 题目背景与核心问题

这道LeetCode hot100题目(编号105)要求我们根据二叉树的前序遍历和中序遍历序列,重建原始的二叉树结构。这是数据结构与算法中非常经典的递归应用问题,也是各大技术面试中的高频考点。

为什么这个问题如此重要?因为在实际开发中,我们经常需要序列化和反序列化二叉树结构。比如:

  • 将内存中的树结构存储到数据库或文件中
  • 通过网络传输树形数据
  • 缓存复杂的树形配置

前序+中序的组合之所以能唯一确定一棵二叉树,关键在于:

  1. 前序遍历的第一个元素必定是根节点
  2. 在中序遍历中找到这个根节点,左侧就是左子树,右侧就是右子树
  3. 递归应用这个规律就能重建整棵树

2. 前序与中序遍历的特性分析

2.1 遍历序列的特点

前序遍历(Preorder)的顺序是:根节点 → 左子树 → 右子树 中序遍历(Inorder)的顺序是:左子树 → 根节点 → 右子树

例如对于二叉树:

3 / \ 9 20 / \ 15 7

其前序遍历为:[3,9,20,15,7] 中序遍历为:[9,3,15,20,7]

2.2 重建二叉树的数学原理

为什么这两个序列能唯一确定二叉树?关键在于:

  1. 前序序列的第一个元素3就是根节点
  2. 在中序序列中找到3,其左侧[9]就是左子树的中序遍历
  3. 前序序列中根节点后的相同数量元素[9]就是左子树的前序遍历
  4. 剩余部分[20,15,7]就是右子树的前序遍历

这个性质可以递归应用到子树的重建中。

3. 递归解法实现细节

3.1 基础递归框架

def buildTree(preorder, inorder): if not preorder or not inorder: return None root_val = preorder[0] root = TreeNode(root_val) root_pos_in_inorder = inorder.index(root_val) left_inorder = inorder[:root_pos_in_inorder] right_inorder = inorder[root_pos_in_inorder+1:] left_preorder = preorder[1:1+len(left_inorder)] right_preorder = preorder[1+len(left_inorder):] root.left = buildTree(left_preorder, left_inorder) root.right = buildTree(right_preorder, right_inorder) return root

3.2 时间复杂度优化

上述解法每次都要在inorder中查找根节点位置,最坏情况下时间复杂度是O(n^2)。我们可以用哈希表提前存储中序遍历的值到索引的映射:

def buildTree(preorder, inorder): inorder_map = {val:idx for idx,val in enumerate(inorder)} def helper(pre_start, pre_end, in_start, in_end): if pre_start > pre_end: return None root_val = preorder[pre_start] root = TreeNode(root_val) root_pos = inorder_map[root_val] left_size = root_pos - in_start root.left = helper(pre_start+1, pre_start+left_size, in_start, root_pos-1) root.right = helper(pre_start+left_size+1, pre_end, root_pos+1, in_end) return root return helper(0, len(preorder)-1, 0, len(inorder)-1)

这样时间复杂度优化到O(n),空间复杂度O(n)用于存储哈希表。

4. 迭代解法与实现

4.1 迭代法思路

递归解法虽然直观,但可能面临栈溢出风险。我们可以用迭代法模拟递归过程:

  1. 用栈保存尚未处理右子树的节点
  2. 用指针跟踪中序遍历的当前位置
  3. 前序遍历序列指导我们创建节点
  4. 中序遍历序列告诉我们何时该添加右孩子

4.2 迭代法实现

def buildTree(preorder, inorder): if not preorder: return None root = TreeNode(preorder[0]) stack = [root] inorder_idx = 0 for i in range(1, len(preorder)): node = stack[-1] if node.val != inorder[inorder_idx]: node.left = TreeNode(preorder[i]) stack.append(node.left) else: while stack and stack[-1].val == inorder[inorder_idx]: parent = stack.pop() inorder_idx += 1 parent.right = TreeNode(preorder[i]) stack.append(parent.right) return root

这种方法的时间复杂度也是O(n),空间复杂度O(n)(最坏情况下栈的大小)。

5. 边界条件与测试用例

5.1 必须考虑的边界情况

  1. 空输入:preorder或inorder为空
  2. 单节点树
  3. 完全左斜树或右斜树
  4. 有重复值的树(题目通常假设值唯一)
  5. 前序和中序序列不匹配的情况

5.2 典型测试用例

# 正常情况 preorder = [3,9,20,15,7] inorder = [9,3,15,20,7] # 左斜树 preorder = [1,2,3] inorder = [3,2,1] # 右斜树 preorder = [1,2,3] inorder = [1,2,3] # 单节点 preorder = [1] inorder = [1] # 空树 preorder = [] inorder = []

6. 实际应用与变种问题

6.1 实际工程应用

  1. 配置文件解析:很多配置文件采用树形结构存储
  2. DOM树重建:网页解析时可能需要从序列化数据重建DOM
  3. 数据库索引:某些数据库索引的序列化和反序列化

6.2 相关变种题目

  1. 从中序和后序遍历构造二叉树(LeetCode 106)
  2. 从前序和后序遍历构造二叉树(LeetCode 889) - 这种情况下树不唯一
  3. 序列化和反序列化二叉树(LeetCode 297)
  4. 根据前序和中序构造二叉搜索树

7. 性能优化与注意事项

7.1 递归深度问题

对于极度不平衡的树,递归解法可能导致栈溢出。解决方法:

  1. 使用迭代法
  2. 设置递归深度限制(Python可用sys.setrecursionlimit)
  3. 对于已知的大数据量,考虑非递归实现

7.2 内存使用优化

  1. 避免在递归过程中不断创建新的数组切片
  2. 使用索引范围来标识当前处理的子序列
  3. 对于特别大的树,考虑使用生成器或迭代器处理序列

7.3 实际编码中的坑

  1. 数组切片时注意边界条件,特别是Python的切片是左闭右开
  2. 确保前序和中序序列的长度一致
  3. 处理空子树时要及时返回None
  4. 在哈希表优化版本中,注意inorder_map要提前构建

8. 扩展思考与进阶学习

8.1 为什么前序+后序不能唯一确定二叉树?

因为无法确定左右子树的划分点。例如: 前序:[1,2,3] 后序:[3,2,1] 可能对应:

1 1 / \ 2 2 \ / 3 3

8.2 如何验证重建的二叉树是否正确?

  1. 对重建的树重新进行前序和中序遍历,检查是否与输入一致
  2. 编写树的结构比较函数
  3. 使用可视化工具绘制二叉树

8.3 对于大规模树的处理

当树非常大时(比如百万节点级别):

  1. 考虑使用非递归实现
  2. 可以分块处理树的各个部分
  3. 可能需要使用磁盘存储中间结果

我在实际面试中遇到这个问题时,通常会先解释清楚原理,然后实现基础递归版本,接着讨论优化方案,最后可能会被要求写迭代版本。这种循序渐进的方式能很好展示问题解决能力。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询