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

news2024/12/23 9:55:28

文章目录

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

题目

标题和出处

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

出处: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.coloradmin.cn/o/1098902.html

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

相关文章

Linux高性能服务器编程 学习笔记 第十四章 进程池和线程池

动态创建子进程或子线程的缺点&#xff1a; 1.动态创建进程或线程比较耗时&#xff0c;这将导致较慢的客户响应。 2.动态创建的子进程或子线程通常只用来为一个客户服务&#xff08;除非我们做特殊处理&#xff09;&#xff0c;这将导致系统上产生大量的进程或线程&#xff0c…

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

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

哈佛教授因果推断力作:《Causal Inference: What If 》pdf下载

因果推断是一项复杂的科学任务&#xff0c;它依赖于多个来源的三角互证和各种方法论方法的应用&#xff0c;是用于解释分析的强大建模工具&#xff0c;同时也是机器学习领域的热门研究方向之一。 今天我要给大家推荐的这本书&#xff0c;正是因果推断领域必读的入门秘籍&#…

《WebGIS快速开发教程第四版》重磅更新

随着笔者夜以继日的不断忙碌&#xff0c;丰富和完善心血之作《WebGIS快速开发教程》&#xff0c;第四版也终于发布了&#xff0c;第四版相比于前三个版本可以用四个字概括那就是“重磅更新”&#xff0c;重磅两个字该如何理解呢&#xff1f; 首先我们来看看更新了哪些内容&…

【MySQL】如何在Linux上安装MySQL

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 Redis 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 MySQL 一、准备Linux服务器二、下载Linux版…

开源欧拉 openEuler 23.09 创新版本发布

导读近日&#xff0c;openEuler 23.09 创新版本正式发布&#xff0c;是社区最新发布的创新版&#xff0c;使用 EulerMaker 构建该版本的的服务器、云计算、边缘计算镜像&#xff0c;版本代码总计 9.1 亿行&#xff0c;相比 openEuler 23.03&#xff0c;新增代码 8900 万行。 新…

Flutter笔记:发布一个电商中文货币显示插件Money Display

Flutter笔记 电商中文货币显示插件 Money Display 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq_28550263/article/details/1338…

功率放大器在超声导波中的应用有哪些

超声导波技术是一种基于声波传播原理的非破坏性检测技术。它通过向被测物体中注入超声波&#xff0c;并接收反射回来的信号&#xff0c;来分析被测物体的内部结构和缺陷情况。在超声导波技术中&#xff0c;功率放大器作为信号源和信号放大器&#xff0c;发挥着重要的作用。下面…

pdf文件大小超过限制怎么办?一招教你压缩pdf文件

我们在制作pdf文档的时候&#xff0c;会加入许多内容&#xff0c;文字、图片等等&#xff0c;素材添加的过多之后就会导致pdf文档特别大&#xff0c;在上传或者储存时&#xff0c;就会特别不方便&#xff0c;所以今天就告诉大家一个pdf压缩&#xff08;https://www.yasuotu.com…

2023年中国氯丁橡胶产量、需求量及进出口现状分析[图]

氯丁橡胶是以2-氯-1,3-丁二烯为主要单体&#xff0c;通过自由基乳液聚合制得的极性合成橡胶。氯丁橡胶具有优异的阻燃性、耐热性、耐候性及耐化学品性&#xff0c;在工业制品、汽车配件、电线电缆护套及粘合剂等领域具有广泛的应用。 2022年&#xff0c;国内氯丁橡胶装置存在2-…

[正式学习java①]——java项目结构,定义类和创建对象,一个标准javabean的书写

目录 一、创建第一个java文件 二、 初始类和对象 三、符合javabean规范的类 一、创建第一个java文件 要想写代码&#xff0c;你得有文件啊 以前的创建方式&#xff1a; 右键新建文本文档&#xff0c;开始写代码&#xff0c;写完改后缀名&#xff0c;保存……这样文件一旦多了…

c语言从入门到实战——C语言数据类型和变量

C语言数据类型和变量 前言1. 数据类型介绍1.1 字符型1.2 整型1.3 浮点型1.4 布尔类型1.5 各种数据类型的长度1.5.1 sizeof操作符1.5.2 数据类型长度1.5.3 sizeof中表达式不计算 2. signed 和 unsigned3. 数据类型的取值范围4. 变量4.1 变量的创建4.2 变量的分类 5. 算术操作符&…

竞赛 深度学习OCR中文识别 - opencv python

文章目录 0 前言1 课题背景2 实现效果3 文本区域检测网络-CTPN4 文本识别网络-CRNN5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习OCR中文识别系统 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;…

如何管理嵌入式开发中产生的数字资产?ACT汽车电子与软件技术周演讲回顾

2023 ATC汽车电子与软件技术周已于8月18日在中国上海落下帷幕。展会现场&#xff0c;龙智技术支持部负责人、Atlassian认证专家叶燕秀与龙智技术工程师邱洁玉共同为观众带来了主题为“更好、更快、更安全&#xff1a;嵌入式开发中的最佳实践与工具链构建”的演讲&#xff0c;分…

UE5射击游戏案例蓝图篇(一)

一、使用到的资源 1.小白人动画包 2.基础武器包 3.虚幻商城免费的模型包 二、角色创建 1.以Character为基类创建出需要的角色&#xff0c;双击打开之后并在已有组件的基础上&#xff0c;添加摄像机臂和摄像机两个组件。添加完成之后可以根据自己的需要调整摄像机臂的位置&…

4. qgis c++二次开发 map canvas介绍

文章目录 前言Map canvasQGis软件中的Map canvas代码添加Map canvasMap Canvas创建和显示 QGis中的QGraphicsItem二次开发中的Item Layer TreeQGis软件中的Layer Tree代码实现layer tree QgsProject(项目管理)QGis软件中的项目管理代码实现 总结 前言 前几篇文章分别介绍了qgi…

软件测试担心失业,如何找一份稳定的技术性工作?没有35岁中年危机!

工作难找&#xff0c;大龄程序员屡次碰壁&#xff0c;感慨并担忧自己的未来没出路&#xff01; 经常有网友发帖留言&#xff1a; 今年1月4号被裁员&#xff0c;至今未找到工作&#xff0c;之前做的是软件测试&#xff0c;boss上沟通了3000多次&#xff0c;投简历200多次&#…

JimuReport 积木报表 v1.6.4 稳定版本正式发布 — 开源免费的低代码报表

项目介绍 一款免费的数据可视化报表&#xff0c;含报表和大屏设计&#xff0c;像搭建积木一样在线设计报表&#xff01;功能涵盖&#xff0c;数据报表、打印设计、图表报表、大屏设计等&#xff01; Web 版报表设计器&#xff0c;类似于excel操作风格&#xff0c;通过拖拽完成报…

高通新骁龙处理器将于明年上半年发布,携四大品牌厂商首发 | 百能云芯

高通&#xff08;Qualcomm&#xff09;即将于10月下旬正式亮相首款以Oryon架构打造的 PC CPU「Snapdragon X系列」&#xff0c;据悉&#xff0c;四大品牌联想、惠普&#xff08;HP&#xff09;、戴尔&#xff08;DELL&#xff09;及宏碁将是首波推出相关PC的品牌厂&#xff0c;…

【JAVA-Day45】Java常用类StringBuffer解析

Java常用类StringBuffer解析 Java常用类StringBuffer解析一、什么是StringBuffer类二、StringBuffer类的方法2.1 append方法2.2 insert方法2.3 delete方法2.4 replace方法2.5 reverse方法2.6 toString方法2.7 capacity方法2.8 length方法 三、StringBuffer类的应用场景深入了解…