算法题 从前序与中序遍历序列构造二叉树

105. 从前序与中序遍历序列构造二叉树

问题描述

给定一棵二叉树的前序遍历 preorder 和中序遍历 inorder,构造并返回二叉树。假设树中无重复元素。

示例
在这里插入图片描述

输入:preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出:[3,9,20,null,null,15,7]

算法思路

递归分治法

  1. 定位根节点
    前序遍历的首元素即为根节点值。
  2. 划分左右子树
    • 在中序遍历中找到根节点位置,左侧为左子树中序序列,右侧为右子树中序序列。
    • 根据左子树长度,在前序遍历中划分左子树前序序列和右子树前序序列。
  3. 递归构建
    • 用左子树的前序+中序序列构建左子树。
    • 用右子树的前序+中序序列构建右子树。

优化
使用哈希表存储中序遍历的值-索引映射,将根节点查找时间优化至 O(1)。

代码实现

import java.util.HashMap;
import java.util.Map;

class Solution {
    // 存储中序遍历的值到索引的映射
    private Map<Integer, Integer> inorderMap;
    // 全局前序遍历数组
    private int[] preorder;
    // 前序遍历索引指针
    private int preIndex;

    public TreeNode buildTree(int[] preorder, int[] inorder) {
        this.preorder = preorder;
        this.inorderMap = new HashMap<>();
        this.preIndex = 0;
        
        // 构建中序遍历哈希表
        for (int i = 0; i < inorder.length; i++) {
            inorderMap.put(inorder[i], i);
        }
        
        return buildSubTree(0, inorder.length - 1);
    }
    
    /**
     * 递归构建子树
     * 
     * @param inStart 中序序列起始索引
     * @param inEnd   中序序列结束索引
     * @return 构建好的子树根节点
     */
    private TreeNode buildSubTree(int inStart, int inEnd) {
        // 终止条件:中序序列为空
        if (inStart > inEnd) return null;
        
        // 创建根节点(前序遍历当前元素)
        int rootValue = preorder[preIndex++];
        TreeNode root = new TreeNode(rootValue);
        
        // 获取根节点在中序序列中的位置
        int rootIndex = inorderMap.get(rootValue);
        
        // 递归构建左子树(中序序列根节点左侧)
        root.left = buildSubTree(inStart, rootIndex - 1);
        // 递归构建右子树(中序序列根节点右侧)
        root.right = buildSubTree(rootIndex + 1, inEnd);
        
        return root;
    }
}

// TreeNode定义
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}

代码注释

代码部分说明
inorderMap存储中序遍历值到索引的映射
preIndex前序遍历索引指针(全局位置)
buildTree()初始化并启动递归构建
buildSubTree(inStart, inEnd)递归构建子树
preorder[preIndex++]创建根节点并前移指针
inorderMap.get(rootValue)获取根节点在中序序列的位置
buildSubTree(inStart, rootIndex-1)构建左子树
buildSubTree(rootIndex+1, inEnd)构建右子树

算法过程

preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]

  1. 初始状态
    preIndex=0, inStart=0, inEnd=4
  2. 根节点
    rootValue=3rootIndex=1(在 inorder 中的位置)
  3. 左子树构建
    • 中序序列:inStart=0, inEnd=0[9]
    • 创建节点 9preIndex=1
  4. 右子树构建
    • 中序序列:inStart=2, inEnd=4[15,20,7]
    • 根节点 20preIndex=2
    • 左子树:15preIndex=3
    • 右子树:7preIndex=4

复杂度分析

  • 时间复杂度:O(n)
    每个节点处理一次,哈希表查找 O(1)
  • 空间复杂度:O(n)
    哈希表 O(n),递归栈深度 O(h)(h 为树高)

关键点

  1. 前序首元素为根
    前序遍历的首元素是当前子树的根节点。
  2. 中序定位左右子树
    中序遍历中根节点左侧为左子树,右侧为右子树。
  3. 递归构建子树
    根据划分的左右子树序列递归构建。
  4. 哈希表优化
    避免每次线性查找根节点位置。
  5. 指针控制
    使用全局 preIndex 按前序顺序处理节点。

测试用例

public static void main(String[] args) {
    Solution solution = new Solution();
    
    // 示例测试
    int[] pre1 = {3,9,20,15,7};
    int[] in1 = {9,3,15,20,7};
    TreeNode root1 = solution.buildTree(pre1, in1);
    printTree(root1); // [3,9,20,null,null,15,7]
    
    // 单节点测试
    int[] pre2 = {1};
    int[] in2 = {1};
    TreeNode root2 = solution.buildTree(pre2, in2);
    printTree(root2); // [1]
    
    // 左斜树测试
    int[] pre3 = {1,2,3};
    int[] in3 = {3,2,1};
    TreeNode root3 = solution.buildTree(pre3, in3);
    printTree(root3); // [1,2,null,3]
    
    // 右斜树测试
    int[] pre4 = {1,2,3};
    int[] in4 = {1,2,3};
    TreeNode root4 = solution.buildTree(pre4, in4);
    printTree(root4); // [1,null,2,null,3]
}

// 辅助方法:打印二叉树(层序遍历)
private static void printTree(TreeNode root) {
    if (root == null) {
        System.out.println("[]");
        return;
    }
    Queue<TreeNode> queue = new LinkedList<>();
    queue.add(root);
    List<String> result = new ArrayList<>();
    while (!queue.isEmpty()) {
        TreeNode node = queue.poll();
        if (node != null) {
            result.add(String.valueOf(node.val));
            queue.add(node.left);
            queue.add(node.right);
        } else {
            result.add("null");
        }
    }
    // 去除末尾多余的null
    int i = result.size() - 1;
    while (i >= 0 && result.get(i).equals("null")) {
        i--;
    }
    System.out.println(result.subList(0, i + 1));
}

常见问题

  1. 为什么需要哈希表?
    哈希表将中序遍历的根节点查找从 O(n) 优化至 O(1),显著提升性能。

  2. 如何处理空子树?
    inStart > inEnd 时返回 null,作为递归终止条件。

  3. 为什么使用全局 preIndex
    前序遍历序列需按顺序处理,全局指针确保每个节点只处理一次。

  4. 能否处理重复元素?
    题目假设无重复元素,若有重复元素需额外处理(如返回任意有效树)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值