当前位置: 首页 > news >正文

leetcode106深度解析:从中序与后序遍历序列构造二叉树

文章目录

  • 一、 题目回顾
  • 二、 核心思路:后序找根,中序划分
  • 三、 图解算法步骤
  • 四、 一种代码实现与深度解析
  • 五、 关键点与复杂度分析
  • 六、 总结与对比 (LC105 vs LC106)

一、 题目回顾

链接:106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)

给定一个二叉树的中序遍历 inorder 和后序遍历 postorder 的结果,请构建该二叉树并返回其根节点。你可以假设树中没有重复的元素。

示例:

inorder  = [9, 3, 15, 20, 7]
postorder = [9, 15, 7, 20, 3]返回的二叉树如下:3/ \9  20/  \15   7

二、 核心思路:后序找根,中序划分

如果你已经理解了 LC105 的“前序找根,中序划分”,那么解决这道题的思路转换就非常自然了。我们只需要把目光从前序遍历转移到后序遍历。

  1. 后序遍历 (Postorder): [[左子树的后序遍历], [右子树的后序遍历], 根节点]

    • 特点: 序列的 最后一个元素 永远是当前子树的 根节点
  2. 中序遍历 (Inorder): [[左子树的中序遍历], 根节点, [右子树的中序遍历]]

    • 特点: (保持不变) 根节点 划分了左、右子树。

我们的新策略是:

  1. 后序遍历 的末尾找到根节点。
  2. 中序遍历 中找到这个根节点的位置,从而划分出左、右子树。
  3. 根据左子树的节点数量,在 后序遍历 序列中确定左、右子树对应的子序列范围。
  4. 这又形成了两个规模更小的相同问题,继续 递归 解决!

三、 图解算法步骤

我们用示例 inorder = [9,3,15,20,7], postorder = [9,15,7,20,3] 来走一遍流程。

第一轮:

  1. 找根节点: postorder 的最后一个元素是 3,所以树的根节点是 3
  2. 中序划分:inorder 中找到 3
    • inorder3 的左边是 [9] (左子树)。
    • inorder3 的右边是 [15, 20, 7] (右子树)。
  3. 后序划分 (关键步骤):
    • 左子树有 1 个节点 (9)。后序遍历的顺序是“左-右-根”,所以 postorder1 个元素 [9] 对应左子树的后序遍历。
    • 右子树有 3 个节点 (15, 20, 7)。所以在 postorder 中,接在左子树后面的 3 个元素 [15, 7, 20] 对应右子树的后序遍历。

Tree Construction Step 2

递归构建左子树:

  • 问题变为:用 inorder = [9]postorder = [9] 构建树。
  • 结论: 得到一个值为 9 的叶子节点,返回。

递归构建右子树:

  • 问题变为:用 inorder = [15, 20, 7]postorder = [15, 7, 20] 构建树。
  • 找根节点: postorder 的最后一个元素是 20
  • 中序划分:inorder 中找到 20,左边是 [15],右边是 [7]
  • 后序划分: 左子树有1个节点,所以 postorder[15] 是左子树;右子树有1个节点,所以 [7] 是右子树。
  • 继续递归… 直到所有节点构建完成。

四、 一种代码实现与深度解析

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode(int x) { val = x; }* }*/
class Solution {// 依然是引入 HashMap 优化,用于 O(1) 查找根节点在中序遍历中的位置Map<Integer, Integer> map;public TreeNode buildTree(int[] inorder, int[] postorder) {// 预处理中序遍历,将 <值, 索引> 存入 HashMapmap = new HashMap<>();for (int i = 0; i < inorder.length; i++) {map.put(inorder[i], i);}// 调用递归函数,初始范围为整个数组// 区间标准统一为左闭右开 [begin, end)return findNode(inorder, 0, inorder.length, postorder, 0, postorder.length);}/*** 递归构建子树的核心函数* @param inorder  中序遍历数组* @param inBegin  当前处理的中序子数组的起始索引(包含)* @param inEnd    当前处理的中序子数组的结束索引(不包含)* @param postorder 后序遍历数组* @param postBegin 当前处理的后序子数组的起始索引(包含)* @param postEnd   当前处理的后序子数组的结束索引(不包含)* @return 构建好的子树的根节点*/public TreeNode findNode(int[] inorder, int inBegin, int inEnd, int[] postorder, int postBegin, int postEnd) {// 基准情况: 当区间为空,无法形成节点,返回 null// 这个条件覆盖了所有递归终止的场景,无需为 size=1 的情况单独处理。if (inBegin >= inEnd || postBegin >= postEnd) {return null;}// 1. 找到根节点:后序遍历区间的最后一个元素int rootVal = postorder[postEnd - 1];TreeNode root = new TreeNode(rootVal);// 2. 在中序遍历中找到根节点的位置int rootIndex = map.get(rootVal);// 3. 计算左子树的节点数量,这是划分后序数组的关键int lenOfLeft = rootIndex - inBegin;// 4. 分治思想:递归构建左子树// 中序遍历的左子树区间: [inBegin, rootIndex)// 后序遍历的左子树区间: 从 postBegin 开始,长度为 lenOfLeft// 即 [postBegin, postBegin + lenOfLeft)root.left = findNode(inorder, inBegin, rootIndex,postorder, postBegin, postBegin + lenOfLeft);// 5. 分治思想:递归构建右子树// 中序遍历的右子树区间: [rootIndex + 1, inEnd)// 后序遍历的右子树区间: 紧跟在左子树之后,且在根节点之前// 即 [postBegin + lenOfLeft, postEnd - 1)root.right = findNode(inorder, rootIndex + 1, inEnd,postorder, postBegin + lenOfLeft, postEnd - 1);// 6. 返回当前构建好的(子)树的根节点return root;}
}

五、 关键点与复杂度分析

  • 后序数组的切分:这是本题相较于 LC105 最需要注意的地方。务必记住后序遍历是 [左, 右, 根]
    • 左子树的后序部分从 postBegin 开始,长度为 lenOfLeft
    • 右子树的后序部分从 postBegin + lenOfLeft 开始,结束于根节点之前,即 postEnd - 1
  • 递归调用顺序:在代码中,我们先递归构建 root.left,再构建 root.right。其实这个顺序可以颠倒,只要你传递给递归函数的区间参数是正确的,最终都能构建出同一棵树。这正是分治思想的魅力所在——子问题之间相互独立
  • 时间复杂度O(N)。N 是节点数。构建 HashMap 为 O(N),之后每个节点只被访问和创建一次,单次操作为 O(1)。
  • 空间复杂度O(N)HashMap 占 O(N),递归栈深度最坏情况下(链表)也为 O(N)。

六、 总结与对比 (LC105 vs LC106)

特性LeetCode 105 (前序 + 中序)LeetCode 106 (后序 + 中序)
找根策略前序遍历的第一个元素 preorder[preBegin]后序遍历的最后一个元素 postorder[postEnd - 1]
划分依据统一使用中序遍历统一使用中序遍历
递归构建root -> left -> rightroot -> left -> right (或 root -> right -> left)
难点计算前序遍历中右子树的起始位置 preBegin + 1 + lenOfLeft计算后序遍历中右子树的区间 [postBegin + lenOfLeft, postEnd - 1]

ft -> right(或root -> right -> left) | | **难点** | 计算前序遍历中右子树的起始位置 preBegin + 1 + lenOfLeft| 计算后序遍历中右子树的区间[postBegin + lenOfLeft, postEnd - 1]` |

这两道题关键不仅是一种递归思路和对二叉树的熟悉,更多的是一种**“分治”**思想

http://www.dtcms.com/a/274101.html

相关文章:

  • leetcode:518. 零钱兑换 II[完全背包]
  • 【网络】Linux 内核优化实战 - net.ipv4.tcp_congestion_control
  • (LeetCode 每日一题) 3169. 无需开会的工作日 ( 排序+贪心 )
  • 力扣_二叉搜索树_python版本
  • 1965–2022年中国大陆高分辨率分部门用水数据集,包含:灌溉用水、工业制造用水、生活用水和火电冷却
  • 【unitrix】 4.21 类型级二进制数基本结构体(types.rs)
  • 李沐动手学深度学习Pytorch-v2笔记【07自动求导代码实现】
  • 进程管理中的队列调度与内存交换机制
  • Jenkins 系统管理与配置
  • 排序算法与前端交互优化
  • 持续集成 简介环境搭建
  • 14 TryHackMe 靶场 Wireshark: The Basics
  • CIU32L051系列 DMA串口无阻塞性收发的实现
  • CentOS 安装 JDK+ NGINX+ Tomcat + Redis + MySQL搭建项目环境
  • Redis5.0.5 漏洞
  • redis的一些疑问
  • windows下安装 redis
  • Redis全栈技术导航:从基础架构到实战案例的完整指南
  • 创客匠人:AI 时代创始人 IP 打造与知识变现的范式迁移
  • 什么是IP关联?跨境卖家如何有效避免IP关联?
  • LeetCode--43.字符串相乘
  • 软件过程模型核心特征与开发流程对照表
  • Android Glide使用与底层机制详解
  • 上位机知识篇---安装包架构
  • imx6ull-系统移植篇2—— U-Boot 命令使用(上)
  • Java 中线程通信方式笔记
  • tailwindCSS === 使用插件自动类名排序
  • ssm框架整合全攻略:从环境搭建到功能实现
  • 什么是Podman?能否替代Docker?Podman快速入门
  • dockerfile 笔记