这是一个非常经典的二叉树构建问题(LeetCode 105. Construct Binary Tree from Preorder and Inorder Traversal)。
你的代码采用了DFS(深度优先搜索)+ 哈希表优化 的策略,逻辑非常清晰。不过,我注意到代码中构建哈希表的地方有一个小小的逻辑反转 需要修正(k:v 应该是 v:k,因为我们需要通过"值"查"下标")。
以下是为你生成的博客文章,我已经修正了那个小瑕疵,并添加了图解说明:
算法图解:如何从前序与中序遍历"复原"二叉树
在二叉树的算法题中,**"构造二叉树"**系列绝对是重难点。今天我们来拆解 LeetCode 105 题:给定两个数组 preorder(前序遍历)和 inorder(中序遍历),如何通过递归的方式完美复原这棵树?
核心思路:分而治之
解决这个问题的关键在于理解两种遍历方式的排布规律:
-
前序遍历 (Preorder) :
[ 根节点 | 左子树所有节点 | 右子树所有节点 ]- 特性 :数组的第一个元素 永远是当前的根节点。
-
中序遍历 (Inorder) :
[ 左子树所有节点 | 根节点 | 右子树所有节点 ]- 特性:一旦我们找到了根节点的位置,它左边的就是左子树,右边的就是右子树。
破局步骤
我们的策略可以总结为 "定位根 -> 切分左右 -> 递归构建":
-
从
preorder的第一个元素拿到 Root。 -
在
inorder中找到这个 Root 的下标 (记为rootIdx)。 -
通过
rootIdx,计算出左子树的长度 (leftLen)。 -
利用
leftLen,我们将preorder和inorder两个数组精确地"切"成两半,分别丢给递归函数去构建左孩子和右孩子。
逻辑图解
假设:
-
preorder = [3, 9, 20, 15, 7] -
inorder = [9, 3, 15, 20, 7]
第一轮递归:
-
找根 :
preorder[0]是3。 -
定位 :在
inorder中找到3的位置,下标是1。 -
切分:
-
左子树 :长度为 1(即
9)。 -
右子树 :剩下的部分(即
15, 20, 7)。
-
此时我们可以确定根节点 3 的左右边界,然后递归下去。
代码实现 (Python)
为了避免每次都在 inorder 中遍历寻找根节点(那样会导致时间复杂度变成 O(N\^2)),我们使用一个 哈希表 (Hash Map) 预先存储 值 -> 下标 的映射,实现 O(1) 的快速查找。
Python
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
# ⚡️ 优化点:构建哈希表,通过"数值"快速查到"中序遍历中的下标"
# 注意:这里是 val : index
idxInorder = {val: i for i, val in enumerate(inorder)}
# 定义 DFS 函数
# pStart, pEnd: 前序遍历的范围
# iStart, iEnd: 中序遍历的范围
def dfs(pStart, pEnd, iStart, iEnd):
# 🛑 终止条件:范围无效(比如 pStart > pEnd),说明子树为空
if pStart > pEnd or iStart > iEnd:
return None
# 1. 建立根节点
root_val = preorder[pStart]
root = TreeNode(root_val)
# 2. 在中序遍历中找到根节点的位置
rootIdx = idxInorder[root_val]
# 3. 计算左子树的节点数量 (关键步骤!)
leftLen = rootIdx - iStart
# 4. 递归构建左右子树
# 左子树:
# Preorder范围: [pStart+1, pStart+leftLen]
# Inorder范围: [iStart, rootIdx-1]
root.left = dfs(pStart + 1, pStart + leftLen, iStart, rootIdx - 1)
# 右子树:
# Preorder范围: [pStart+leftLen+1, pEnd]
# Inorder范围: [rootIdx+1, iEnd]
root.right = dfs(pStart + leftLen + 1, pEnd, rootIdx + 1, iEnd)
return root
return dfs(0, len(preorder)-1, 0, len(inorder)-1)
难点解析:索引计算
代码中最容易晕的地方就是递归时的索引范围,一定要死记这个公式:
-
左子树长度 (
leftLen) =rootIdx(根在中序的位置) -iStart(中序的起始位置)。 -
左孩子的前序范围 :从
pStart + 1开始,长度为leftLen,所以结束于pStart + leftLen。 -
右孩子的前序范围 :紧接着左孩子之后,即
pStart + leftLen + 1开始,直到pEnd。
复杂度分析
-
时间复杂度:O(N)
-
我们需要遍历所有节点一次来构建树。
-
哈希表的查找是 O(1) 的,构建哈希表也是 O(N)。
-
-
空间复杂度:O(N)
-
哈希表占用了 O(N) 的空间。
-
递归调用栈在最坏情况(链状树)下也需要 O(N) 的空间。
-
总结
这道题是理解 分治算法 (Divide and Conquer) 在树结构中应用的绝佳案例。
只要记住 "前序定根,中序定界" 这八字口诀,配合哈希表提速,就能轻松拿下!
希望这篇解析能帮你彻底搞懂二叉树的构建!