LeetCode 第106题:从中序与后序遍历序列构造二叉树

111 阅读6分钟

LeetCode 第106题:从中序与后序遍历序列构造二叉树

题目描述

给定两个整数数组 inorderpostorder ,其中 inorder 是二叉树的中序遍历postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树

难度

中等

题目链接

点击在LeetCode中查看题目

示例

示例 1:

示例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 <= 3000
  • postorder.length == inorder.length
  • -3000 <= inorder[i], postorder[i] <= 3000
  • inorderpostorder 都由 不同 的值组成
  • postorder 中每一个值都在 inorder
  • inorder 保证是树的中序遍历
  • postorder 保证是树的后序遍历

解题思路

方法:递归 + 哈希表优化

这道题是第105题的变体,需要根据中序遍历和后序遍历序列构造二叉树。

关键点:

  1. 后序遍历的最后一个元素是根节点
  2. 在中序遍历中找到根节点位置,可将数组分为左右子树
  3. 使用哈希表存储中序遍历中元素的索引,优化查找效率
  4. 递归构建左右子树

具体步骤:

  1. 创建哈希表,存储中序遍历中每个元素的索引
  2. 从后序遍历的最后一个元素开始,确定根节点
  3. 在中序遍历中找到根节点位置,将数组分为左右子树
  4. 递归构建左子树和右子树
  5. 返回构建好的根节点

时间复杂度:O(n),其中n是树中节点的数量 空间复杂度:O(n),用于存储哈希表和递归调用栈

图解思路

递归构建过程分析表

步骤当前后序范围当前中序范围根节点值左子树大小右子树大小
1[9,15,7,20,3][9,3,15,20,7]313
2[9][9]900
3[15,7,20][15,20,7]2011
4[15][15]1500
5[7][7]700

数组分割示意图

遍历类型完整数组左子树根节点右子树
中序[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 ms40.2 MB代码结构清晰,易于理解
Python52 ms18.9 MB代码简洁,使用列表推导式优化
C++16 ms26.1 MB执行效率最高,内存占用适中

代码亮点

  1. 🎯 使用哈希表优化查找效率,将查找根节点位置的时间从O(n)降至O(1)
  2. 💡 巧妙利用后序遍历的特性(最后一个元素是根节点)
  3. 🔍 精确计算子树范围,确保递归构建的正确性
  4. 🎨 代码结构清晰,变量命名规范,易于理解

常见错误分析

  1. 🚫 后序遍历和中序遍历的子数组范围计算错误
  2. 🚫 没有正确处理空树或单节点树的边界情况
  3. 🚫 忘记使用哈希表优化,导致时间复杂度过高
  4. 🚫 递归终止条件设置不当,可能导致栈溢出

解法对比

解法时间复杂度空间复杂度优点缺点
递归+哈希表O(n)O(n)实现简单,效率高需要额外空间存储哈希表
递归(不用哈希表)O(n²)O(n)实现简单,空间占用小查找根节点位置效率低
迭代O(n)O(n)避免递归栈溢出实现复杂,不直观

相关题目