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

news2024/9/23 23:23:14

文章目录

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

题目

标题和出处

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

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

难度

5 级

题目描述

要求

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

示例

示例 1:

示例 1

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

示例 2:

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

数据范围

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

解法一

思路和算法

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

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

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

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

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

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

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

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

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

代码

class Solution {
    Map<Integer, Integer> inorderIndices = new HashMap<Integer, Integer>();
    int[] inorder;
    int[] postorder;

    public TreeNode buildTree(int[] inorder, int[] postorder) {
        this.inorder = inorder;
        this.postorder = postorder;
        int length = inorder.length;
        for (int i = 0; i < length; i++) {
            inorderIndices.put(inorder[i], i);
        }
        return buildTree(0, 0, length);
    }

    public TreeNode buildTree(int inorderStart, int postorderStart, int nodesCount) {
        if (nodesCount == 0) {
            return null;
        }
        int rootVal = postorder[postorderStart + nodesCount - 1];
        TreeNode root = new TreeNode(rootVal);
        int inorderRootIndex = inorderIndices.get(rootVal);
        int leftNodesCount = inorderRootIndex - inorderStart;
        int rightNodesCount = nodesCount - 1 - leftNodesCount;
        root.left = buildTree(inorderStart, postorderStart, leftNodesCount);
        root.right = buildTree(inorderRootIndex + 1, postorderStart + leftNodesCount, rightNodesCount);
        return root;
    }
}

复杂度分析

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

解法二

思路和算法

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

如果将中序遍历序列反序,则可以得到反序中序遍历序列,依次遍历右子树、根结点和左子树;如果将后序遍历序列反序,则可以得到反序后序遍历序列,依次遍历根结点、右子树和左子树。利用反序序列的性质,可以对中序遍历序列和后序遍历序列反向遍历并构造二叉树。注意构造二叉树的过程中只是对中序遍历序列和后序遍历序列反向遍历,并没有将中序遍历序列和后序遍历序列反序。

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

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

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

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

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

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

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

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

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

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

图 1

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

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

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

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

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

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

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

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

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

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

  10. 当遍历到后序遍历序列的 5 5 5 时,下一个结点值和中序遍历序列的当前结点值不同,因此创建结点 5 5 5,作为结点 9 9 9 的右子结点,并将结点 5 5 5 入栈。

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

代码

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

复杂度分析

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

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

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

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

相关文章

互联网行业汇总

互联网行业汇总&#xff0c;全网最全&#xff01;选行业不愁 从事互联网选什么行业&#xff1f;这似乎是很多朋友的困惑。 所以这里给大家把互联网行业做个细致的汇总&#xff0c;每个行业列举几个典型的APP&#xff0c;简单拆解下各自的盈利模式&#xff0c;希望能给大家提供参…

了解Netty,从IO开始

java程序员要想升级高级工程师或者成为架构师&#xff0c;绕不开Netty的学习&#xff0c;就算你不做IM即时通信&#xff0c;也不是网络编程的工作岗位&#xff0c;仅仅只是CRUD程序员&#xff0c;当你想要了解一下Dubbo、Redis、kafka、rabbitMQ、ES、zookeeper、nginx等等的底…

群硕与Microsoft Dynamics全球团队密切协作,加速ERP产品迭代

群硕具备强大的软件研发能力&#xff0c;搭建自动化测试平台&#xff0c;保证高质量交付。 ERP系统的引入被视为企业走向数字化转型的关键一步。 此系统有助于实现企业内部资源与外部资源的整合&#xff0c;通过软件把人、财、物、产、供、销及相应的物流、信息流、资金流、管…

大数据之LibrA数据库系统部署方案

组网方案 基本概念 FusionInsight LibrA集群的组网方案中包含如下节点&#xff0c;如表1所示。 网络平面类型 FusionInsight LibrA整个系统网络划分为2个平面&#xff0c;即业务平面和管理平面&#xff0c;两个平面之间采用物理隔离的方式进行部署&#xff0c;保证业务、管理…

Lua快速入门教程

文章目录 1、Linux安装Lua2、语法练习2.1、变量2.2、循环2.3、函数2.4、数组2.5、迭代器2.6、Table操作2.7、Lua 模块与包2.8、加载机制2.9、Lua 元表(Metatable) 3、Lua 协同程序(coroutine)4、文件IO操作4.1、简单模式4.2、完全模式 5、错误处理 内容来源菜鸟教程&#xff0c…

软考-访问控制技术原理与应用

本文为作者学习文章&#xff0c;按作者习惯写成&#xff0c;如有错误或需要追加内容请留言&#xff08;不喜勿喷&#xff09; 本文为追加文章&#xff0c;后期慢慢追加 by 2023年10月 访问控制概念 访问控制是计算机安全的一个重要组成部分&#xff0c;用于控制用户或程序如…

Linux | gdb的基本使用

目录 前言 一、调试文件的生成 二、调试指令 1、选择调试文件 2、查看代码 3、运行代码 4、断点 5、打印与常显示 6、其他 总结 前言 前面我们学习了如何使用gcc/g来进行对代码进行编译&#xff0c;本章我们将使用gdb来对代码进行调试&#xff0c;学习本章的前提是有…

实验室用超声波清洗机哪家好

随着超声波清洗机在实验室得到广泛应用&#xff0c;超声波清洗机厂家也随之增多&#xff0c;品牌、型号更是数不胜数&#xff0c;价格相差也是十分悬殊。那么面对纷繁复杂的实验室超声波清洗机市场&#xff0c;实验室用超声波清洗机哪家好&#xff1f;小编推荐国内知名超声波清…

《动手学深度学习 Pytorch版》 9.3 深度循环神经网络

将多层循环神经网络堆叠在一起&#xff0c;通过对几个简单层的组合&#xff0c;产生一个灵活的机制。其中的数据可能与不同层的堆叠有关。 9.3.1 函数依赖关系 将深度架构中的函数依赖关系形式化&#xff0c;第 l l l 个隐藏层的隐状态表达式为&#xff1a; H t ( l ) ϕ l …

用宝塔部署静态html页面

云服务器安装宝塔面板搭建LNMP环境&#xff0c;可以看另一文零基础搭建个人网站详细流程。 本文主要介绍用宝塔部署静态页面&#xff0c;比较简单&#xff0c;以部署一个用户协议为例。 首先&#xff0c;去“网站”中“添加站点”。有域名用域名&#xff0c;没域名用IP。 然后…

NSSCTF做题(10)

叫10好听一点&#xff0c;就是补9的 第7页的内容 [SWPUCTF 2022 新生赛]ez_sql get传参说是不安全&#xff0c;那就只能用post了 有回显了&#xff0c;两个假的flag 发现万能密码 1 or 11#变成了 11# 11# 1 11#1# 11# 11# 发现or和空格都无了&#xff0c;union也过滤 …

零基础Linux_19(进程信号)产生信号+Core_Dump+保存信号

目录 1. 信号前期知识 1.1 生活中的信号 1.2 Linux中的信号 1.3 信号概念 1.4 信号处理方法的注册 2. 产生信号 2.1 通过终端按键产生信号 2.2 调用系统调用向进程发信号 2.3 软件条件产生信号 2.4 硬件异常产生信号 3. 核心转储Core Dump 4. 保存信号 4.1 信号在…

day08_面向对象_封装_继承_this_super_访问修饰符

今日内容 1.作业 2.封装 3.继承 4.this和super 5.访问修饰符 零、复习 成员变量和局部变量(画表格) this的作用 this是当前对象,当前方法的调用者this可以调用属性和方法this.属性, this.方法名(),this() 构造方法的作用和语法特征 作用: 创建对象,属性初始化特征: 没有返回值,…

数据结构和算法(13):优先级队列

概述 按照事先约定的优先级&#xff0c;可以始终高效查找并访问优先级最高数据项的数据结构&#xff0c;也统称作优先级队列 优先级队列将操作对象限定于当前的全局极值者。 根据数据对象之间相对优先级对其进行访问的方式&#xff0c;与此前的访问方式有着本质区别&#xf…

PostgreSQL与MySQL数据库对比:适用场景和选择指南

数据库是现代应用程序的基石之一&#xff0c;而在选择合适的数据库管理系统&#xff08;DBMS&#xff09;时&#xff0c;开发者常常会面临着许多选择。在这方面&#xff0c;PostgreSQL和MySQL是两个备受瞩目的选项。本文将深入研究这两者之间的异同&#xff0c;并为您提供适用场…

疯狂星期四的营销策略是什么?如何复制?

你知道疯狂星期四吗&#xff1f;它的策略是什么&#xff1f;如何对标它写一个类似的方案呢&#xff1f; 1、消费者心理 KFC疯狂星期四的核心目标消费者是对价格敏感的年轻人和家庭消费者。他们寻求物有所值的美食体验&#xff0c;希望在合理的价格范围内享受到美味的食物。通过…

Unity之ShaderGraph如何实现UV抖动

前言 今天我们通过噪波图来实现一个UV抖动的效果。 如下图所示&#xff1a; 关键节点 Time&#xff1a;提供对着色器中各种时间参数的访问 UV&#xff1a;提供对网格顶点或片段的UV坐标的访问。可以使用通道下拉参数选择输出值的坐标通道。 SimpleNoise&#xff1a;根据…

论文导读 | 支持事务与图分析的图存储系统

事务系统保证了系统的数据一致性&#xff0c;确保事务更新的原子性或是不同事务之间的数据隔离性等在多线程并发环境下所必不可少的ACID特性。而在今天快速变化的商业环境下&#xff0c;诸如物流和供应链&#xff0c;金融风控和欺诈检测等场景都需要图分析系统提供对数据动态更…

Node.js在Python中的应用实例解析

随着互联网的发展&#xff0c;数据爬取成为了获取信息的重要手段。本文将以豆瓣网为案例&#xff0c;通过技术问答的方式&#xff0c;介绍如何使用Node.js在Python中实现数据爬取&#xff0c;并提供详细的实现代码过程。 Node.js是一个基于Chrome V8引擎的JavaScript运行时环境…

SEAL:RLWE-BFV 开源算法库

参考文献&#xff1a; GitHub - microsoft/SEAL: Microsoft SEAL is an easy-to-use and powerful homomorphic encryption library.[HS13] Halevi S, Shoup V. Design and implementation of a homomorphic-encryption library[J]. IBM Research (Manuscript), 2013, 6(12-15…