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

文章目录

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

题目

标题和出处

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

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

难度

5 级

题目描述

要求

给定两个整数数组 preorder \texttt{preorder} preorder inorder \texttt{inorder} inorder,其中 preorder \texttt{preorder} preorder 是二叉树的前序遍历, inorder \texttt{inorder} inorder 是同一个树的中序遍历,请构造并返回二叉树。

示例

示例 1:

示例 1

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

示例 2:

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

数据范围

  • 1 ≤ preorder.length ≤ 3000 \texttt{1} \le \texttt{preorder.length} \le \texttt{3000} 1preorder.length3000
  • inorder.length = preorder.length \texttt{inorder.length} = \texttt{preorder.length} inorder.length=preorder.length
  • -3000 ≤ preorder[i], inorder[i] ≤ 3000 \texttt{-3000} \le \texttt{preorder[i], inorder[i]} \le \texttt{3000} -3000preorder[i], inorder[i]3000
  • preorder \texttt{preorder} preorder inorder \texttt{inorder} inorder 都由不同的值组成
  • inorder \texttt{inorder} inorder 中的每个值均出现在 preorder \texttt{preorder} preorder
  • preorder \texttt{preorder} preorder 保证为二叉树的前序遍历序列
  • inorder \texttt{inorder} inorder 保证为二叉树的中序遍历序列

解法一

思路和算法

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

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

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

前序遍历序列的第一个元素值为根结点值,只要在中序遍历序列中定位到根结点值的下标,即可得到左子树中的结点数和右子树中的结点数。对于左子树和右子树,也可以在给定的前序遍历序列和中序遍历序列中分别得到对应的子序列,根据子序列构造相应的子树。当子树构造完毕时,原始二叉树即可构造完毕。

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

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

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

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

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

代码

class Solution {Map<Integer, Integer> inorderIndices = new HashMap<Integer, Integer>();int[] preorder;int[] inorder;public TreeNode buildTree(int[] preorder, int[] inorder) {this.preorder = preorder;this.inorder = inorder;int length = preorder.length;for (int i = 0; i < length; i++) {inorderIndices.put(inorder[i], i);}return buildTree(0, 0, length);}public TreeNode buildTree(int preorderStart, int inorderStart, int nodesCount) {if (nodesCount == 0) {return null;}int rootVal = preorder[preorderStart];TreeNode root = new TreeNode(rootVal);int inorderRootIndex = inorderIndices.get(rootVal);int leftNodesCount = inorderRootIndex - inorderStart;int rightNodesCount = nodesCount - 1 - leftNodesCount;root.left = buildTree(preorderStart + 1, inorderStart, leftNodesCount);root.right = buildTree(preorderStart + 1 + leftNodesCount, inorderRootIndex + 1, rightNodesCount);return root;}
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 preorder \textit{preorder} preorder inorder \textit{inorder} inorder 的长度,即二叉树的结点数。将中序遍历序列中的每个结点值与下标的对应关系存入哈希表需要 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 inorder \textit{inorder} inorder 的长度,即二叉树的结点数。空间复杂度主要是递归调用的栈空间以及哈希表空间,因此空间复杂度是 O ( n ) O(n) O(n)

解法二

思路和算法

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

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

  1. 结点 y y y 是结点 x x x 的左子结点;

  2. 结点 x x x 没有左子结点,结点 y y y 是结点 x x x 的右子结点,或者结点 y y y 是结点 x x x 的某个祖先结点的右子结点。

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

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

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

  • 如果前序遍历序列的上一个结点值和中序遍历序列的当前结点值相同,则前序遍历序列的上一个结点没有左子结点,属于第 2 种情况。

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

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

以下用一个例子说明构造二叉树的过程。已知二叉树的前序遍历序列是 [ 1 , 10 , 8 , 9 , 5 , 7 , 15 , 12 , 20 , 13 ] [1, 10, 8, 9, 5, 7, 15, 12, 20, 13] [1,10,8,9,5,7,15,12,20,13],中序遍历序列是 [ 9 , 5 , 8 , 10 , 7 , 1 , 12 , 15 , 13 , 20 ] [9, 5, 8, 10, 7, 1, 12, 15, 13, 20] [9,5,8,10,7,1,12,15,13,20],二叉树如图所示。

图 1

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

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

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

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

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

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

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

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

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

  9. 当遍历到前序遍历序列的 20 20 20 时,上一个结点值是 12 12 12,和中序遍历序列的当前结点值相同,因此遍历中序遍历序列并将栈内的结点 12 12 12 15 15 15 出栈,此时中序遍历序列的下标移动到 8 8 8。创建结点 20 20 20,将结点 20 20 20 作为结点 15 15 15 的右子结点,并将结点 20 20 20 入栈。

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

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

代码

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

复杂度分析

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

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

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

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

相关文章

<三>Qt斗地主游戏开发:主界面初始化显示

1. 主界面效果 效果关键点&#xff1a; 1&#xff09;拖动标题栏可实现主界面拖动 2&#xff09;logo图标名称及主界面背景 3&#xff09;最小化及关闭 2.思路分析 1&#xff09;背景图片及logo图标的设定比较简单&#xff0c;通过stylesheet即可实现。通过QWidget的拖动即可实…

算法leetcode|84. 柱状图中最大的矩形(rust重拳出击)

文章目录 84. 柱状图中最大的矩形&#xff1a;样例 1&#xff1a;样例 2&#xff1a;提示&#xff1a; 分析&#xff1a;题解&#xff1a;rust&#xff1a;go&#xff1a;c&#xff1a;python&#xff1a;java&#xff1a; 84. 柱状图中最大的矩形&#xff1a; 给定 n 个非负整…

【git的使用方法】——上传文件到gitlab仓库

先进入到你克隆下来的仓库的目录里面 比如&#xff1a;我的仓库名字为zhuox 然后将需要上传推送的文件拷贝到你的克隆仓库下 这里的话我需要拷贝的项目是t3 输入命令ls&#xff0c;就可以查看该文件目录下的所有文件信息 然后输入git add 文件名 我这边输入的是 &#x…

处理ElementUI组件默认样式多次重复问题

问题截图&#xff1a; 解决办法&#xff1a; 在postcss.config.js文件里添加配置项&#xff1a; module.exports {plugins: {autoprefixer: {},cssnano: {} //添加这行代码}, } 处理结果&#xff1a; github issues&#xff1a; https://github.com/ElemeFE/element/is…

【LeetCode刷题(数组and排序)】:存在重复元素

给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 &#xff0c;返回 true &#xff1b;如果数组中每个元素互不相同&#xff0c;返回 false 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3,1] 输出&#xff1a;true 示例 2&#xff1a; 输入&#xff1a;nums [1,2…

WorkPlus即时通讯办公软件,助力企业实现移动化办公

在移动互联网的时代背景下&#xff0c;企业对于高效的移动平台需求日益迫切。WorkPlus作为领先品牌&#xff0c;致力于为企业打造卓越的移动平台&#xff0c;助力企业实现协作与效率的突破。本文将探讨WorkPlus如何通过其专业的解决方案&#xff0c;为企业打造无限可能的移动办…

14.8 Socket 一收一发通信

通常情况下我们在编写套接字通信程序时都会实现一收一发的通信模式&#xff0c;当客户端发送数据到服务端后&#xff0c;我们希望服务端处理请求后同样返回给我们一个状态值&#xff0c;并以此判断我们的请求是否被执行成功了&#xff0c;另外增加收发同步有助于避免数据包粘包…

【Kotlin精简】第4章 函数

1 简介 函数是用来运行代码的载体&#xff0c;可以在一个函数里编写很多行代码&#xff0c;当运行这个函数时&#xff0c;函数中的所有代码会全部运行。 Kotlin中的函数同Java完全面向对象的规则不太一样&#xff0c;在Kotlin的世界里&#xff0c;函数也是准C位的&#xff0c;…

微服务架构 | 超时管理

INDEX LSA 级别与全年停机时间速查表LSA 级别实战TP 性能超时时间设计原则 LSA 级别与全年停机时间速查表 计算公式&#xff1a;60 * 60 * 24 * 365 * (1-LSA) 31,536,000‬ * (1-LSA) 系统级别LSA级别全年停机时间099.999%5分钟099.99%52分钟199.9%8.8小时299%3.65 天 LSA…

基于 ACK Fluid 的混合云优化数据访问(五):自动化跨区域中心数据分发

作者&#xff1a;车漾 前文回顾&#xff1a; 本系列将介绍如何基于 ACK Fluid 支持和优化混合云的数据访问场景&#xff0c;相关文章请参考&#xff1a; -基于 ACK Fluid 的混合云优化数据访问&#xff08;一&#xff09;&#xff1a;场景与架构 -基于 ACK Fluid 的混合云优…

科研上新 | 第2期:可驱动3D肖像生成;阅读文本密集图像的大模型;文本控制音色;基于大模型的推荐智能体

编者按&#xff1a;欢迎阅读“科研上新”栏目&#xff01;“科研上新”汇聚了微软亚洲研究院最新的创新成果与科研动态。在这里&#xff0c;你可以快速浏览研究院的亮点资讯&#xff0c;保持对前沿领域的敏锐嗅觉&#xff0c;同时也能找到先进实用的开源工具。 本期内容速览 …

基于yolov2深度学习网络的猫脸检测识别matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 load yolov2.mat% 加载训练好的目标检测器 img_size [224,224]; imgPath test/; % 图…

至强服务器BIOS/UEFI驱动开发笔记

至强服务器BIOS/UEFI驱动开发笔记 驱动开发基础Hello UEFI Driver 项目选择项目位置初始化驱动代码文件结构驱动程序入口和基本功能导入AMI工程AMI平台Hello UEFI Driver 编译问题测试结果打印设备列表继续开发`HelloWorldSupported`函数依赖配置使用脚本编译编译测试此DXE驱动…

SQL如何导入数据以及第一次上机作业

如何导入excel数据 首先得学会导入数据 使用excel格式不需要改成其它格式&#xff08;如csv&#xff0c;txt&#xff09;&#xff0c;因为你改了到时候还是会报错&#xff08;实践过使用Sum统计总数一直说我数据格式有问题&#xff09; 首先右键TSGL数据库->任务->导入数…

C++前缀和算法应用:矩形区域不超过 K 的最大数值和

基础知识点 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 题目 给你一个 m x n 的矩阵 matrix 和一个整数 k &#xff0c;找出并返回矩阵内部矩形区域的不超过 k 的最大数值和。 题目数据保证总会存在一个数值和不超过 k 的矩形区域。 示例 1&#x…

机器学习-概述与贝叶斯算法

机器学习的一般步骤&#xff1a;数据搜集、数据清洗、特征工程、数学建模。数据划分&#xff1a;训练集、验证集、测试集。K折交叉验证&#xff1a;解决数据量不够大问题&#xff0c;解决参数调优问题。深度学习不用做特征工程&#xff0c;传统机器学习要。损失函数&#xff0c…

深圳寄包裹到德国

深圳&#xff0c;作为全球最发达的城市之一&#xff0c;以其高效的物流服务在全球范围内享有盛名。如果你正在寻找一种方式将包裹从深圳寄送到德国&#xff0c;那么本文将为你提供详细的步骤和建议。 第一步&#xff1a;了解国际邮寄的基本信息 首先&#xff0c;你需要了解包裹…

Bitquiz重塑Learn to Earn热潮,用户零投入让学习创造价值

Axie 带来的暴富效应、StepN 带来的出圈效应&#xff0c;近期Bigtime 在熊市中的大火&#xff0c;为加密参与者带来的赚取效应&#xff0c;X to Earn 重新成为整个市场关注的重点&#xff0c;GameFi 再次站在了风口浪尖。 大家开始寻找下一个Bigtime&#xff0c;希望能够抓住一…

低代码技术这么香,如何把它的开发特点发挥到极致?

前言 什么是低代码技术&#xff1f; 低代码是一种可视化软件开发方法&#xff0c;通过最少的编码更快地交付应用程序。图形用户界面和拖放功能使开发过程的各个方面自动化&#xff0c;消除了对传统计算机编程方法的依赖。 文章目录 前言低代码平台怎么选&#xff1f;用友Yonbu…

压缩炸弹,Java怎么防止

一、什么是压缩炸弹&#xff0c;会有什么危害 1.1 什么是压缩炸弹 压缩炸弹(ZIP)&#xff1a;一个压缩包只有几十KB&#xff0c;但是解压缩后有几十GB&#xff0c;甚至可以去到几百TB&#xff0c;直接撑爆硬盘&#xff0c;或者是在解压过程中CPU飙到100%造成服务器宕机。虽然…