LeetCode 第106题:从中序与后序遍历序列构造二叉树
题目描述
给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。
难度
中等
题目链接
示例
示例 1:
输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
输出:[3,9,20,null,null,15,7]
示例 2:
输入:inorder = [-1], postorder = [-1]
输出:[-1]
提示
1 <= inorder.length <= 3000postorder.length == inorder.length-3000 <= inorder[i], postorder[i] <= 3000inorder和postorder都由 不同 的值组成postorder中每一个值都在inorder中inorder保证是树的中序遍历postorder保证是树的后序遍历
解题思路
方法:递归 + 哈希表优化
这道题是第105题的变体,需要根据中序遍历和后序遍历序列构造二叉树。
关键点:
- 后序遍历的最后一个元素是根节点
- 在中序遍历中找到根节点位置,可将数组分为左右子树
- 使用哈希表存储中序遍历中元素的索引,优化查找效率
- 递归构建左右子树
具体步骤:
- 创建哈希表,存储中序遍历中每个元素的索引
- 从后序遍历的最后一个元素开始,确定根节点
- 在中序遍历中找到根节点位置,将数组分为左右子树
- 递归构建左子树和右子树
- 返回构建好的根节点
时间复杂度:O(n),其中n是树中节点的数量 空间复杂度:O(n),用于存储哈希表和递归调用栈
图解思路
递归构建过程分析表
| 步骤 | 当前后序范围 | 当前中序范围 | 根节点值 | 左子树大小 | 右子树大小 |
|---|---|---|---|---|---|
| 1 | [9,15,7,20,3] | [9,3,15,20,7] | 3 | 1 | 3 |
| 2 | [9] | [9] | 9 | 0 | 0 |
| 3 | [15,7,20] | [15,20,7] | 20 | 1 | 1 |
| 4 | [15] | [15] | 15 | 0 | 0 |
| 5 | [7] | [7] | 7 | 0 | 0 |
数组分割示意图
| 遍历类型 | 完整数组 | 左子树 | 根节点 | 右子树 |
|---|---|---|---|---|
| 中序 | [9,3,15,20,7] | [9] | 3 | [15,20,7] |
| 后序 | [9,15,7,20,3] | [9] | 3 | [15,7,20] |
代码实现
C# 实现
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
public class Solution {
public TreeNode BuildTree(int[] inorder, int[] postorder) {
if (inorder == null || inorder.Length == 0 ||
postorder == null || postorder.Length == 0) {
return null;
}
// 创建哈希表,存储中序遍历中每个元素的索引
Dictionary<int, int> inorderMap = new Dictionary<int, int>();
for (int i = 0; i < inorder.Length; i++) {
inorderMap[inorder[i]] = i;
}
return BuildTreeHelper(
inorder, 0, inorder.Length - 1,
postorder, 0, postorder.Length - 1,
inorderMap
);
}
private TreeNode BuildTreeHelper(
int[] inorder, int inStart, int inEnd,
int[] postorder, int postStart, int postEnd,
Dictionary<int, int> inorderMap) {
if (inStart > inEnd || postStart > postEnd) {
return null;
}
// 后序遍历的最后一个元素是根节点
int rootVal = postorder[postEnd];
TreeNode root = new TreeNode(rootVal);
// 在中序遍历中找到根节点的位置
int rootIndex = inorderMap[rootVal];
// 计算左子树的节点数量
int leftSubtreeSize = rootIndex - inStart;
// 递归构建左子树
root.left = BuildTreeHelper(
inorder, inStart, rootIndex - 1,
postorder, postStart, postStart + leftSubtreeSize - 1,
inorderMap
);
// 递归构建右子树
root.right = BuildTreeHelper(
inorder, rootIndex + 1, inEnd,
postorder, postStart + leftSubtreeSize, postEnd - 1,
inorderMap
);
return root;
}
}
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, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
if not inorder or not postorder:
return None
# 创建哈希表,存储中序遍历中每个元素的索引
inorder_map = {val: idx for idx, val in enumerate(inorder)}
def build_helper(in_start, in_end, post_start, post_end):
if in_start > in_end or post_start > post_end:
return None
# 后序遍历的最后一个元素是根节点
root_val = postorder[post_end]
root = TreeNode(root_val)
# 在中序遍历中找到根节点的位置
root_idx = inorder_map[root_val]
# 计算左子树的节点数量
left_size = root_idx - in_start
# 递归构建左子树
root.left = build_helper(
in_start, root_idx - 1,
post_start, post_start + left_size - 1
)
# 递归构建右子树
root.right = build_helper(
root_idx + 1, in_end,
post_start + left_size, post_end - 1
)
return root
return build_helper(0, len(inorder) - 1, 0, len(postorder) - 1)
C++ 实现
/**
* 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>& inorder, vector<int>& postorder) {
if (inorder.empty() || postorder.empty()) {
return nullptr;
}
// 创建哈希表,存储中序遍历中每个元素的索引
unordered_map<int, int> inorderMap;
for (int i = 0; i < inorder.size(); i++) {
inorderMap[inorder[i]] = i;
}
return buildTreeHelper(
inorder, 0, inorder.size() - 1,
postorder, 0, postorder.size() - 1,
inorderMap
);
}
private:
TreeNode* buildTreeHelper(
vector<int>& inorder, int inStart, int inEnd,
vector<int>& postorder, int postStart, int postEnd,
unordered_map<int, int>& inorderMap) {
if (inStart > inEnd || postStart > postEnd) {
return nullptr;
}
// 后序遍历的最后一个元素是根节点
int rootVal = postorder[postEnd];
TreeNode* root = new TreeNode(rootVal);
// 在中序遍历中找到根节点的位置
int rootIndex = inorderMap[rootVal];
// 计算左子树的节点数量
int leftSubtreeSize = rootIndex - inStart;
// 递归构建左子树
root->left = buildTreeHelper(
inorder, inStart, rootIndex - 1,
postorder, postStart, postStart + leftSubtreeSize - 1,
inorderMap
);
// 递归构建右子树
root->right = buildTreeHelper(
inorder, rootIndex + 1, inEnd,
postorder, postStart + leftSubtreeSize, postEnd - 1,
inorderMap
);
return root;
}
};
执行结果
C# 实现
- 执行用时:88 ms
- 内存消耗:40.2 MB
Python 实现
- 执行用时:52 ms
- 内存消耗:18.9 MB
C++ 实现
- 执行用时:16 ms
- 内存消耗:26.1 MB
性能对比
| 语言 | 执行用时 | 内存消耗 | 特点 |
|---|---|---|---|
| C# | 88 ms | 40.2 MB | 代码结构清晰,易于理解 |
| Python | 52 ms | 18.9 MB | 代码简洁,使用列表推导式优化 |
| C++ | 16 ms | 26.1 MB | 执行效率最高,内存占用适中 |
代码亮点
- 🎯 使用哈希表优化查找效率,将查找根节点位置的时间从O(n)降至O(1)
- 💡 巧妙利用后序遍历的特性(最后一个元素是根节点)
- 🔍 精确计算子树范围,确保递归构建的正确性
- 🎨 代码结构清晰,变量命名规范,易于理解
常见错误分析
- 🚫 后序遍历和中序遍历的子数组范围计算错误
- 🚫 没有正确处理空树或单节点树的边界情况
- 🚫 忘记使用哈希表优化,导致时间复杂度过高
- 🚫 递归终止条件设置不当,可能导致栈溢出
解法对比
| 解法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 递归+哈希表 | O(n) | O(n) | 实现简单,效率高 | 需要额外空间存储哈希表 |
| 递归(不用哈希表) | O(n²) | O(n) | 实现简单,空间占用小 | 查找根节点位置效率低 |
| 迭代 | O(n) | O(n) | 避免递归栈溢出 | 实现复杂,不直观 |