二叉树题目:从前序与后序遍历序列构造二叉树

文章目录

  • 题目
    • 标题和出处
    • 难度
    • 题目描述
      • 要求
      • 示例
      • 数据范围
  • 前言
  • 解法一
    • 思路和算法
    • 代码
    • 复杂度分析
  • 解法二
    • 思路和算法
    • 代码
    • 复杂度分析

题目

标题和出处

标题:从前序与后序遍历序列构造二叉树

出处:889. 从前序与后序遍历序列构造二叉树

难度

7 级

题目描述

要求

给定两个整数数组 preorder \texttt{preorder} preorder postorder \texttt{postorder} postorder,其中 preorder \texttt{preorder} preorder 是一个具有无重复值的二叉树的前序遍历, postorder \texttt{postorder} postorder 是同一个树的后序遍历,请构造并返回二叉树。

如果存在多个答案,返回其中任何一个。

示例

示例 1:

示例 1

输入: preorder = [1,2,4,5,3,6,7], postorder = [4,5,2,6,7,3,1] \texttt{preorder = [1,2,4,5,3,6,7], postorder = [4,5,2,6,7,3,1]} preorder = [1,2,4,5,3,6,7], postorder = [4,5,2,6,7,3,1]
输出: [1,2,3,4,5,6,7] \texttt{[1,2,3,4,5,6,7]} [1,2,3,4,5,6,7]

示例 2:

输入: preorder = [1], postorder = [1] \texttt{preorder = [1], postorder = [1]} preorder = [1], postorder = [1]
输出: [1] \texttt{[1]} [1]

数据范围

  • 1 ≤ preorder.length ≤ 30 \texttt{1} \le \texttt{preorder.length} \le \texttt{30} 1preorder.length30
  • 1 ≤ preorder[i] ≤ preorder.length \texttt{1} \le \texttt{preorder[i]} \le \texttt{preorder.length} 1preorder[i]preorder.length
  • preorder \texttt{preorder} preorder 中所有值都不同
  • postorder.length = preorder.length \texttt{postorder.length} = \texttt{preorder.length} postorder.length=preorder.length
  • 1 ≤ postorder[i] ≤ postorder.length \texttt{1} \le \texttt{postorder[i]} \le \texttt{postorder.length} 1postorder[i]postorder.length
  • postorder \texttt{postorder} postorder 中所有值都不同
  • 保证 preorder \texttt{preorder} preorder postorder \texttt{postorder} postorder 是同一个二叉树的前序遍历和后序遍历

前言

当二叉树中的每个结点值各不相同时,给定二叉树的前序遍历与中序遍历,或者给定二叉树的中序遍历与后序遍历,都可以唯一地构造出二叉树。但是给定二叉树的前序遍历与后序遍历,则可能有多种符合要求的二叉树。

由于答案可能不唯一,因此在构造二叉树时需要做特殊处理,使得答案为可能的答案之一。在确保得到正确答案的前提下,将答案限定在一种可能。

解法一

思路和算法

由于二叉树中的每个结点值各不相同,因此可以根据结点值唯一地确定结点。

二叉树的前序遍历的方法为:依次遍历根结点、左子树和右子树,对于左子树和右子树使用同样的方法遍历。

二叉树的后序遍历的方法为:依次遍历左子树、右子树和根结点,对于左子树和右子树使用同样的方法遍历。

前序遍历序列的第一个元素值与后序遍历序列的最后一个元素值都是根结点值。如果左子树和右子树都不为空,则前序遍历序列的第二个元素值为左子结点值,后序遍历序列的倒数第二个元素值为右子结点值,由于后序遍历序列中每个子树的根结点总是最后被访问的,因此只要在后序遍历序列中定位到左子结点值的下标,即可得到左子树中的结点数和右子树中的结点数。对于左子树和右子树,也可以在给定的前序遍历序列和后序遍历序列中分别得到对应的子序列,根据子序列构造相应的子树。

如果前序遍历序列的第二个元素值与后序遍历序列的倒数第二个元素值相同,则只有一个子树不为空,左子树非空和右子树非空都是可能的情况。为了将答案限定在一种可能,当只有一个子树不为空时,规定左子树不为空,则左子树中的结点数为二叉树中的结点数减 1 1 1,右子树中的结点数为 0 0 0。对于左子树,使用同样的方法构造。

上述构造二叉树的过程是一个递归分治的过程。将二叉树分成根结点、左子树和右子树三部分,首先构造左子树和右子树,然后构造原始二叉树,构造左子树和右子树是原始问题的子问题。

分治的终止条件是子序列为空,此时构造的子树为空。当子序列不为空时,首先得到根结点值以及左子树和右子树对应的子序列,然后递归地构造左子树和右子树。

实现方面有两点需要注意。

  1. 在后序遍历序列中定位左子结点值的下标时,简单的做法是遍历整个序列寻找左子结点值,该做法的时间复杂度较高。可以使用哈希表存储每个结点值在后序遍历序列中的下标,即可在 O ( 1 ) O(1) O(1) 的时间内定位到任意结点值在后序遍历序列中的下标。

  2. 对于左子树和右子树的构造需要使用子序列,此处的子序列实质为下标连续的子数组。为了降低时间复杂度和空间复杂度,使用开始下标和子数组长度确定子数组,则不用新建数组和复制数组元素,而且可以复用哈希表存储的每个结点值在后序遍历序列中的下标信息。

代码

class Solution {Map<Integer, Integer> postorderIndices = new HashMap<Integer, Integer>();int[] preorder;int[] postorder;public TreeNode constructFromPrePost(int[] preorder, int[] postorder) {this.preorder = preorder;this.postorder = postorder;int length = preorder.length;for (int i = 0; i < length; i++) {postorderIndices.put(postorder[i], i);}return constructFromPrePost(0, 0, length);}public TreeNode constructFromPrePost(int preorderStart, int postorderStart, int nodesCount) {if (nodesCount == 0) {return null;}int rootVal = preorder[preorderStart];TreeNode root = new TreeNode(rootVal);if (nodesCount == 1) {return root;}int leftChildVal = preorder[preorderStart + 1];int postorderLeftChildIndex = postorderIndices.get(leftChildVal);int leftNodesCount = postorderLeftChildIndex - postorderStart + 1;int rightNodesCount = nodesCount - 1 - leftNodesCount;root.left = constructFromPrePost(preorderStart + 1, postorderStart, leftNodesCount);root.right = constructFromPrePost(preorderStart + 1 + leftNodesCount, postorderLeftChildIndex + 1, rightNodesCount);return root;}
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 preorder \textit{preorder} preorder postorder \textit{postorder} postorder 的长度,即二叉树的结点数。将后序遍历序列中的每个结点值与下标的对应关系存入哈希表需要 O ( n ) O(n) O(n) 的时间,构造二叉树也需要 O ( n ) O(n) O(n) 的时间。

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 preorder \textit{preorder} preorder postorder \textit{postorder} postorder 的长度,即二叉树的结点数。空间复杂度主要是递归调用的栈空间以及哈希表空间,因此空间复杂度是 O ( n ) O(n) O(n)

解法二

思路和算法

使用迭代的方法构造二叉树,需要充分利用二叉树遍历的性质,考虑遍历序列中相邻结点的关系。

对于前序遍历序列中的两个相邻的值 x x x y y y,其对应结点的关系一定是以下两种情况之一:

  1. 结点 y y y 是结点 x x x 的左子结点,或者结点 x x x 没有左子结点且结点 y y y 是结点 x x x 的右子结点;

  2. 结点 x x x 是叶结点,结点 y y y 是结点 x x x 的某个祖先结点的右子结点。

对于第 1 种情况,在后序遍历序列中, y y y x x x 前面。对于第 2 种情况,在后序遍历序列中, x x x y y y 前面。

判断前序遍历序列中的两个相邻的值属于哪一种情况,需要借助后序遍历序列。由于后序遍历访问根结点在访问左右子树之后,因此可以比较前序遍历序列的上一个结点值和后序遍历序列的当前结点值,判断属于哪一种情况:

  • 如果前序遍历序列的上一个结点值和后序遍历序列的当前结点值不同,则前序遍历序列的上一个结点不是叶结点,属于第 1 种情况;

  • 如果前序遍历序列的上一个结点值和后序遍历序列的当前结点值相同,则前序遍历序列的上一个结点是叶结点,属于第 2 种情况。

注意在第 1 种情况下,后序遍历序列的结点值顺序恰好和前序遍历序列的结点值顺序相反,可以使用栈实现反转序列。

具体做法是,遍历前序遍历序列,对于每个值分别创建结点,将每个结点作为栈顶结点的左子结点,并将每个结点入栈,直到栈顶结点值等于后序遍历序列的当前结点值。然后遍历后序遍历序列并依次将栈内的结点出栈,直到栈顶结点值和后序遍历序列的当前结点值不同,此时前序遍历序列的当前值对应的结点为栈顶结点的右子结点,将当前结点入栈。然后对前序遍历序列和后序遍历序列的其余值继续执行上述操作,直到遍历结束时,二叉树构造完毕。

以下用示例 1 说明构造二叉树的过程。已知二叉树的前序遍历序列是 [ 1 , 2 , 4 , 5 , 3 , 6 , 7 ] [1, 2, 4, 5, 3, 6, 7] [1,2,4,5,3,6,7],后序遍历序列是 [ 4 , 5 , 2 , 6 , 7 , 3 , 1 ] [4, 5, 2, 6, 7, 3, 1] [4,5,2,6,7,3,1]

初始时,后序遍历序列的下标是 0 0 0。以下将后序遍历序列的下标处的值称为后序遍历序列的当前结点值。

  1. 将前序遍历序列的下标 0 0 0 的元素 1 1 1 作为根结点值创建根结点,并将根结点入栈。

  2. 当遍历到前序遍历序列的 2 2 2 时,上一个结点值和后序遍历序列的当前结点值不同,因此创建结点 2 2 2,作为结点 1 1 1 的左子结点,并将结点 2 2 2 入栈。

  3. 当遍历到前序遍历序列的 4 4 4 时,上一个结点值和后序遍历序列的当前结点值不同,因此创建结点 4 4 4,作为结点 2 2 2 的左子结点,并将结点 4 4 4 入栈。

  4. 当遍历到前序遍历序列的 5 5 5 时,上一个结点值是 4 4 4,和后序遍历序列的当前结点值相同,因此遍历后序遍历序列并将栈内的结点 4 4 4 出栈,此时后序遍历的下标移动到 1 1 1。创建结点 5 5 5,将结点 5 5 5 作为结点 2 2 2 的右子结点,并将结点 5 5 5 入栈。

  5. 当遍历到前序遍历序列的 3 3 3 时,上一个结点值是 5 5 5,和后序遍历序列的当前结点值相同,因此遍历后序遍历序列并将栈内的结点 5 5 5 2 2 2 出栈,此时后序遍历的下标移动到 3 3 3。创建结点 3 3 3,将结点 3 3 3 作为结点 1 1 1 的右子结点,并将结点 3 3 3 入栈。

  6. 当遍历到前序遍历序列的 6 6 6 时,上一个结点值和后序遍历序列的当前结点值不同,因此创建结点 6 6 6,作为结点 3 3 3 的左子结点,并将结点 6 6 6 入栈。

  7. 当遍历到前序遍历序列的 7 7 7 时,上一个结点值是 6 6 6,和后序遍历序列的当前结点值相同,因此遍历后序遍历序列并将栈内的结点 6 6 6 出栈,此时后序遍历的下标移动到 4 4 4。创建结点 7 7 7,将结点 7 7 7 作为结点 3 3 3 的右子结点,并将结点 7 7 7 入栈。

此时遍历结束,二叉树构造完毕。

代码

class Solution {public TreeNode constructFromPrePost(int[] preorder, int[] postorder) {int length = preorder.length;TreeNode root = new TreeNode(preorder[0]);Deque<TreeNode> stack = new ArrayDeque<TreeNode>();stack.push(root);int postorderIndex = 0;for (int i = 1; i < length; i++) {TreeNode prev = stack.peek();TreeNode curr = new TreeNode(preorder[i]);if (prev.val != postorder[postorderIndex]) {prev.left = curr;stack.push(curr);} else {while (stack.peek().val == postorder[postorderIndex]) {stack.pop();postorderIndex++;}prev = stack.peek();prev.right = curr;stack.push(curr);}}return root;}
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 preorder \textit{preorder} preorder postorder \textit{postorder} postorder 的长度,即二叉树的结点数。前序遍历序列和后序遍历序列各需要遍历一次,构造二叉树需要 O ( n ) O(n) O(n) 的时间。

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 preorder \textit{preorder} preorder postorder \textit{postorder} postorder 的长度,即二叉树的结点数。空间复杂度主要是栈空间,取决于二叉树的高度,最坏情况下二叉树的高度是 O ( n ) O(n) O(n)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/624704.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

react项目运行卡在编译:您当前运行的TypeScript版本不受@TypeScript eslint/TypeScript estree的官方支持

1.问题 错误信息具体如下&#xff1a; 搜索了一下&#xff0c;是typescript版本的问题&#xff0c;提示我版本需要在3.3.0和4.5.0中间&#xff0c;我查看了package.json&#xff0c;显示版本为4.1.3&#xff0c;然后一直给我提示我的版本是4.9.5&#xff0c;全局搜索一下&…

FPGA——时序分析与约束(Quartus II)

FPGA时序分析与约束 FPGA结构基础数据传输模型Quartus II 时序报告Quartus II 中TimeQuest的操作实操 时序分析&#xff1a;通过分析FPGA内部各个存储器之间的数据和时钟传输路径&#xff0c;来分析数据延迟和时钟延迟的关系&#xff0c;保证所有寄存器都可以正确寄存数据。 数…

Ubuntu配置NFS客户端和服务端详解——手把手配置

Ubuntu配置NFS客户端和服务端 如果您想实现远程访问并修改 ROS 主机中 Ubuntu 上的文件&#xff0c;可以通过 NFS挂载的方式。虚拟机上的 Ubuntu 系统可以通过 NFS 的方式来访问 ROS 主机中Ubuntu 系统的文件&#xff0c;NFS 分为服务器挂载和客户端访问。这里虚拟机上的 Ubun…

基于多反应堆的高并发服务器【C/C++/Reactor】(中)HttpRequest 提取请求行、解析请求行和优化 以及解析请求头并存储

### 知识点1&#xff1a;读取网络数据 客户端发送给服务器的通信数据通过封装的bufferSocketRead函数读取读取的数据存储在struct Buffer结构体实例中&#xff0c;可将该实例作为参数传递给解析函数 回顾Buffer.c中的bufferSocketRead函数 // 写内存 2.接收套接字数据 int b…

腾讯云服务器新版活动报价表,3年核5年适合长期使用

腾讯云服务器优惠价格表_一年、3年、包月和1小时费用_最新报价 腾讯云服务器租用优惠价格表&#xff1a;轻量应用服务器2核2G3M价格62元一年、2核2G4M价格118元一年&#xff0c;540元三年、2核4G5M带宽218元一年&#xff0c;2核4G5M带宽756元三年、轻量4核8G12M服务器646元15个…

【复现】Tenda信息泄露漏洞_19

目录 一.概述 二 .漏洞影响 三.漏洞复现 1. 漏洞一&#xff1a; 四.修复建议&#xff1a; 五. 搜索语法&#xff1a; 六.免责声明 一.概述 Tenda远端WEB管理是为了在外网&#xff08;其他网络&#xff09;可以访问路由器&#xff0c;从而进行管理。 电脑可以通过网线连接…

HTML---JAVAscript对向和原型链

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 一.JavaSceipt对象概述 JavaScript是一种基于对象的编程语言&#xff0c;每个值都是一个对象。JavaScript中的对象是一种无序的键值对集合&#xff0c;其中每个键都是唯一的。 JavaScript的…

浅析Pe2shellcode

编者注&#xff1a;本文仅供学习研究&#xff0c;严禁从事非法活动&#xff0c;任何后果由使用者本人负责。 前言 众所周知&#xff0c;对shellcode免杀是很流行的技术&#xff0c;但是直接对exe的免杀方法相对稀缺&#xff0c;如果我们能将exe转为shellcode&#xff0c;然后用…

基于ssm的中文学习系统的设计与实现+jsp论文

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本中文学习系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信息&am…

电脑桌面便签在哪设置?备忘录软件哪个好?

好记性不如烂笔头&#xff01;相信很多打工族在电脑面前办公的时候&#xff0c;都需要随时记录工作中的事项&#xff0c;有的用TXT记录&#xff0c;有的手写笔记&#xff0c;还有一些用电脑桌面便签类软件。而当我们待办事项繁多的时候&#xff0c;手写或文本记录并不能有效帮我…

去不了哈尔滨? 来看这里VR全景线上云体验

如果你无法亲自前往哈尔滨&#xff0c;那么不要失望&#xff0c;因为现在有一种全新的方式让你在家就能领略到哈尔滨的美丽景色。 冰城客户端、哈尔滨新闻网承办的“激情迎亚冬 冰雪暖世界——2024年哈尔滨冰雪乐园” 运用720云VR打造的沉浸式体验产品正式上线&#xff0c;将带…

[我的Rust库更新]get_local_info 0.2.3

今天收到西安城市开发者社区的文章收录通知&#xff0c;谢谢社区的肯定。 随即发布0.2.3版本&#xff0c;增加峰值算法。 get_local_info是一个获取linux本地信息的Rust三方库&#xff0c;其目标是降低获取本地linux系统信息的难度。支持银河麒麟10、UOS、鸿蒙等国产系统。 项…

利用HTML和CSS实现的浮动布局

代码如下 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><style>*{m…

MPU机制与实现详解

目录 MPU机制与实现详解 Partition元素-MPU Partition实现元素OSApplication Partition元素-RTE MPU机制与实现详解 1、freedom from interference 此概念来自ISO26262-1&#xff1a;多个元素之间没有可能导致违反安全目标的级联故障&#xff0c;称之为免于干涉。 在左侧的…

案例129:基于微信小程序的外卖商城平台设计与实现

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

pybind11实现numpy和OpenCV Mat的数据交互

1、编译安装pybind11 下载源代码&#xff1a;https://github.com/pybind/pybind11&#xff0c; 文档&#xff1a;https://pybind11.readthedocs.io/en/stable/compiling.html 解压后进入到命令行&#xff0c;如果有conda环境&#xff0c;就先进入想要的conda环境&#xff0c…

Poi实现根据word模板导出-文本段落篇

最近在做word模板导出的需求&#xff0c;本来意为是很简单&#xff0c;做起来才发现细节上有很多东西处理起来还是比较麻烦的&#xff08;客户要求太多&#xff01;&#xff01;&#xff01;&#xff09; 因此我把涉及到基于word模板导出的这部分整理了一下&#xff0c;大家直…

2024年,前端开发者,不妨看看鸿蒙开发

从大环境也好&#xff0c;行业发展也好&#xff0c;过去10年的前端技术晋级路径已经彻底失效。我在去年看了一眼考公的职位&#xff0c;没有任何一个岗位可以由前端胜任&#xff0c;而如果在后端领域比较资深的话&#xff0c;进可以做技术咨询&#xff0c;退可以考架构公务编。…

【基于 InternLM 和 LangChain 搭建你的知识库】学习笔记

学习参考文档【基于 InternLM 和 LangChain 搭建你的知识库】 学习参考链接【书生・浦语大模型实战营第三课作业(基础进阶)】 理论 实战 收集原始数据 收集2018年-2020年几年间的优秀数学建模论文 修改脚本文件&#xff0c;测试文件 作业 复现课程知识库助手搭建过程 La…

PDF文件的创建时间可以修改吗?分享你一个简单的小技巧

PDF文件的创建时间能修改吗&#xff1f; PDF文件的创建时间是指该文件首次被创建或生成的日期和时间。这个时间被存储在PDF文件的元数据中&#xff0c;可以通过某些软件查看和修改。 在某些情况下&#xff0c;我们需要将PDF文件的创建时间修改为特定的日期和时间。例如&#…