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

文章目录

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

题目

标题和出处

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

出处: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…

算法|每日一题|只出现一次的数字Ⅲ|位运算

260.只出现一次的数字III 原题地址&#xff1a;力扣每日一题&#xff1a;260.只出现一次的数字III 之前整理过本题及其扩展&#xff0c;详细说明了思路和做法&#xff0c;链接如下&#xff1a; 只出现一次的数字I&#xff0c;II&#xff0c;III class Solution {public int[] s…

C++ vector 自定义排序规则(vector<vector<int>>、vector<pair<int,int>>)

vector< int > vector<int> vec{1,2,3,4};//默认从小到大排序 1234 sort(vec.begin(),vec.end()); //从大到小排序 4321 sort(vec.begin(),vec.end(),greater<int>());二维向量vector<vector< int >> vector<vector<int>> vec{{0…

Ubuntu下安装Go

如果要添加PATH环境变量&#xff0c;建议添加至~/.bashrc目录下&#xff0c;而不是这篇文章所说的~/.profile或/etc/profile&#xff1a; Download and install - Go Official Website

【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;另外增加收发同步有助于避免数据包粘包…

android 如何确定MediaCodec输出的确切YUV格式

在Android中&#xff0c;您可以通过MediaCodec的MediaFormat对象来确定输出的确切YUV格式。以下是如何获取这些信息的步骤&#xff1a; 1、初始化并配置MediaCodec&#xff1a; 在设置MediaCodec之后&#xff0c;例如在调用configure()后或在第一次接收到输出缓冲区后&#x…

android 13.0 静默安装app和静默卸载app功能实现

1.概述 在13.0的产品开发中,对于调用pm的系统api实现静默安装已经受限,并且在8.0 9.0以后由于系统对于权限控制越来越严格 所以说通过adb shell 来安装卸载app都受到了限制但是又不想通过调用系统接口 弹出对话框 让用户同意后在安装 所以就需要 在系统中增加静默安装app的接…

【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 的混合云优…

android apk 加固后重新签名

APP安全检测&#xff0c;代码有泄露风险&#xff0c;需要加固 &#xff0c;找个几个平台&#xff0c;最终在腾讯加固平台有免费10个基础加固 &#xff0c;随后对APP进行加固&#xff0c;加固之后发现需要重新签名 1.查看项目中build.gradle文件中buildToolVerson 中Android S…

科研上新 | 第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数据库->任务->导入数…