从中序与后序遍历序列构造二叉树(LeetCode 106)
题目链接:从中序与后序遍历序列构造二叉树(LeetCode 106)
难度:中等
1. 题目描述
给你两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历,postorder 是同一棵树的后序遍历,请你构造并返回这颗二叉树。
要求:
- 树中没有重复元素
- 数组长度 1 <= n <= 3000
- -3000 <= inorder[i], postorder[i] <= 3000
- inorder 和 postorder 都由唯一值组成,且 postorder 中的每个值都在 inorder 中出现
- inorder 保证是树的中序遍历
- postorder 保证是树的后序遍历
示例:
输入: inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
输出: [3,9,20,null,null,15,7]
解释: 二叉树结构为根节点3,左子节点9,右子节点20(左15,右7)。
输入: inorder = [2,1], postorder = [2,1]
输出: [1,2]
解释: 根节点1,左子节点2。
2. 问题分析
2.1 规律
- 中序遍历:左子树 -> 根 -> 右子树。
- 后序遍历:左子树 -> 右子树 -> 根。
- 后序遍历的最后一个元素总是当前子树的根节点。
- 在中序遍历中找到根节点的位置,其左侧是左子树的中序,右侧是右子树的中序。
- 由于值唯一,可用哈希表存储中序值到索引的映射,避免每次线性搜索。
- 核心问题:如何递归划分左右子树,并高效构建树结构?
2.2 递归思路
我们使用递归算法:
- 预构建哈希表
inorder_map,存储每个值在中序数组中的索引。 - 使用后序数组从末尾开始弹出根节点(因为后序最后是根)。
- 递归函数
helper(in_start, in_end):- 如果
in_start > in_end,返回 None(空树)。 - 取当前根值
root_val = postorder[post_idx],post_idx -= 1。 - 创建根节点。
- 找到根在中序的索引
idx = inorder_map[root_val]。 - 先递归构建右子树
root.right = helper(idx + 1, in_end)(因为后序中右子树在左子树之后,从后往前先遇到右子树)。 - 再递归构建左子树
root.left = helper(in_start, idx - 1)。
- 如果
- 初始调用:
helper(0, n-1),post_idx初始化为n-1。
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, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
n = len(inorder)
if n == 0:
return None
inorder_map = {val: idx for idx, val in enumerate(inorder)}
post_idx = n - 1
def helper(in_start: int, in_end: int) -> Optional[TreeNode]:
nonlocal post_idx
if in_start > in_end:
return None
root_val = postorder[post_idx]
post_idx -= 1
root = TreeNode(root_val)
idx = inorder_map[root_val]
root.right = helper(idx + 1, in_end)
root.left = helper(in_start, idx - 1)
return root
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>& inorder, vector<int>& postorder) {
int n = inorder.size();
if (n == 0) return nullptr;
unordered_map<int, int> inorder_map;
for (int i = 0; i < n; ++i) {
inorder_map[inorder[i]] = i;
}
int post_idx = n - 1;
function<TreeNode*(int, int)> helper = [&](int in_start, int in_end) -> TreeNode* {
if (in_start > in_end) return nullptr;
int root_val = postorder[post_idx];
--post_idx;
TreeNode* root = new TreeNode(root_val);
int idx = inorder_map[root_val];
root->right = helper(idx + 1, in_end);
root->left = helper(in_start, idx - 1);
return root;
};
return helper(0, n - 1);
}
};
4. 复杂度分析
- 时间复杂度:O(n),构建哈希表 O(n),递归遍历每个节点一次 O(n)。
- 空间复杂度:O(n),哈希表 O(n),递归栈最坏 O(n)(链状树)。
5. 总结
- 树重建问题 + 中序+后序 → 递归划分子树是首选。
- 核心维护后序索引和中序映射,很通用。
- 类似从中序+前序构建树(LeetCode 105),但调整构建顺序(前序先左后右,后序先右后左)。
- 可扩展到其他遍历组合或序列化问题。
复习
面试经典150题[013]:除自身以外数组的乘积(LeetCode 238)
面试经典150题[043]:字母异位词分组(LeetCode 49)
面试经典150题[058]:两数相加(LeetCode 2)