LeetCode 105. 从前序与中序遍历序列构造二叉树:分治递归思路详解
前言
LeetCode 105 是二叉树构造题中的经典代表,也是面试高频题。
很多人知道:
前序可以找到根节点,中序可以划分左右子树。
但是写代码时,经常会卡在:
- 左子树区间怎么算?
- 右子树区间怎么算?
- 为什么需要哈希表?
这篇文章就把整个递归过程一步一步讲清楚。
一、题目描述
给定:
- 前序遍历数组 preorder
- 中序遍历数组 inorder
要求恢复原来的二叉树。
例如:
text
preorder
3 9 20 15 7
inorder
9 3 15 20 7
恢复后的树:
text
3
/ \
9 20
/ \
15 7
二、先理解两种遍历的特点
前序遍历
顺序:
根
左
右
因此:
数组第一个元素一定是当前子树的根节点。
例如:
3 9 20 15 7
根就是:
3
中序遍历
顺序:
左
根
右
例如:
9 3 15 20 7
找到根:
3
左边:
9
全部属于左子树。
右边:
15 20 7
全部属于右子树。
所以:
中序遍历负责:
划分左右子树。
三、为什么能够递归?
恢复整棵树,其实就是不断重复下面三件事:
第一步:
前序第一个元素找根。
第二步:
中序找到根的位置。
第三步:
递归恢复左右子树。
整个过程完全一致。
所以天然适合递归。
四、如何切分区间?
这是整道题最关键的地方。
例如:
preorder
3 9 20 15 7
inorder
9 3 15 20 7
根:
3
在中序的位置:
9 | 3 | 15 20 7
左子树节点数量:
1
因此:
前序中:
根 左子树 右子树
3 | 9 | 20 15 7
左子树也只有一个节点。
所以:
左子树区间就确定了。
右子树自然也就确定了。
整个递归都是利用:
左子树节点数量。
来不断切分前序数组。
五、为什么使用哈希表?
如果每次都去中序数组查找根节点:
python
inorder.index(root)
时间复杂度:
O(n)
递归过程中会查很多次。
整体退化成:
O(n²)
因此提前建立:
python
value -> index
哈希表。
例如:
python
{
9:0,
3:1,
15:2,
20:3,
7:4
}
以后查找:
O(1)
整体复杂度降低到:
O(n)
六、递归流程
整个递归可以概括为四步。
第一步
前序第一个元素作为当前根节点。
第二步
通过哈希表找到根节点在中序中的位置。
第三步
根据左子树节点数量切分前序区间。
第四步
递归构造左右子树。
直到区间为空。
返回 None。
七、完整代码
python
class Solution:
def buildTree(self, preorder, inorder):
index = {value: i for i, value in enumerate(inorder)}
def build(pre_left, pre_right, in_left, in_right):
if pre_left > pre_right:
return None
root_val = preorder[pre_left]
root = TreeNode(root_val)
mid = index[root_val]
left_size = mid - in_left
root.left = build(
pre_left + 1,
pre_left + left_size,
in_left,
mid - 1
)
root.right = build(
pre_left + left_size + 1,
pre_right,
mid + 1,
in_right
)
return root
return build(
0,
len(preorder) - 1,
0,
len(inorder) - 1
)
八、复杂度分析
时间复杂度
O(n)
每个节点只访问一次。
空间复杂度
O(n)
主要来自:
- 哈希表
- 递归栈
九、高频易错点
1、没有建立哈希表
直接使用:
python
index()
会导致:
O(n²)
2、左右区间切分错误
真正决定切分的是:
左子树节点数量
而不是:
根节点的位置。
3、遗漏终止条件
必须写:
python
if pre_left > pre_right:
return None
否则容易数组越界。
4、不断切片数组
例如:
python
preorder[:k]
虽然能写出来。
但是每次都会复制数组。
空间和时间都会增加。
面试中更推荐:
使用左右边界表示区间。
十、总结
这道题其实就是不断重复三个动作:
- 前序找到根节点。
- 中序划分左右子树。
- 根据左子树节点数量切分前序区间,递归继续构造。
掌握了这种「前序确定根、中序划分左右、递归分治」的思路,不仅能解决 LeetCode 105,还能应对很多二叉树重建类题目,是二叉树递归中的经典模板。