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

文章目录

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

题目

标题和出处

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构】排序之归并排序与计数排序

个人主页 &#xff1a; zxctsclrjjjcph 文章封面来自&#xff1a;艺术家–贤海林 如有转载请先通知 目录 1. 前言2. 归并排序2.1 递归实现2.1.1 分析2.1.2 代码实现 2.2 非递归实现2.2.1 分析2.2.2 代码实现 3. 计数排序3.1 分析3.2 代码实现 4. 附代码4.1 Sort.h4.2 Sort.c4.3…

【Intel校企实践】猫狗大战

作业简介&#xff1a; 问题描述&#xff1a; ​ 在这个问题中&#xff0c;你将面临一个经典的机器学习分类挑战——猫狗大战。你的任务是建立一个分类模型&#xff0c;能够准确地区分图像中是猫还是狗。 预期解决方案&#xff1a; ​ 你的目标是通过训练一个机器学习模型&a…

【深蓝学院】移动机器人运动规划--第1章 运动规划介绍与地图构建--笔记

文章目录 1. Course introduction2. Course Outline2.1 课程概览2.2 课程算法概览2.2.1 基于搜索的前端2.2.2 基于采样的前端2.2.3 满足动力学约束的路径搜索2.2.4 后端轨迹优化 3. 地图表示3.1 Occupancy grid map占用栅格地图3.2 八叉树地图3.3 Voxel hashing&#xff08;体素…

虾皮开通:如何在虾皮(Shopee)平台上开通店铺详细步骤

在全球电商市场的竞争中&#xff0c;越来越多的卖家选择在虾皮&#xff08;Shopee&#xff09;平台上开设店铺。作为东南亚地区最大的电子商务平台之一&#xff0c;虾皮提供了一个便捷的销售渠道&#xff0c;吸引了数百万的买家和卖家。如果您想在虾皮上开设自己的店铺&#xf…

《动手学深度学习》学习笔记 第10章 注意力机制

本系列为《动手学深度学习》学习笔记 书籍链接&#xff1a;动手学深度学习 笔记是从第四章开始&#xff0c;前面三章为基础知识&#xff0c;有需要的可以自己去看看 关于本系列笔记&#xff1a; 书里为了让读者更好的理解&#xff0c;有大篇幅的描述性的文字&#xff0c;内容很…

利用c 原生头文件完成JPEG全流程编码

骄傲一下&#xff0c;经过一个多月的努力&#xff0c;终于完成jpeg的全套编码。经验证此程序可以把摄像头yuv信号转为JPG图片。现在的程序还不完美&#xff0c;只能对长和宽尺寸是16倍数的信号转码。而且转码速度太慢&#xff0c;一帧1280720的图片要2秒多。此程序只能对yuv420…

静态路由高级特性(HCIA)

目录 一、静态路由高级特性 1、路由条目六要素 2、路由分类 3、静态路由配置命令 &#xff08;1&#xff09;静态路由中下一跳MA和P2P区别 4、静态路由加路由表条件 5、permanent特性 二、路由冗余和负载 1、控制层面control plane 2、数据层面data plane 路由操控精髓&#xf…

测试用例的设计(超详细总结)

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号【互联网杂货铺】&#xff0c;回复 1 &#xff0c;免费获取软件测试全套资料 1. 测试用例的概念 软件测试人员向被测试系统提供的一组数据的集合&#xff0c;包括 测试环境、测试步骤、…

深入探究Python Collections模块:高效数据结构解决方案

前言 这几天刷leetcode题时&#xff0c;看到题解中有这样一行代码collections.defaultdict(list)&#xff0c;不明白是啥意思&#xff0c;平时开发的脚本中未遇到&#xff0c;借着这个机会&#xff0c;学习一下collections模块的用法。 collections 这个模块实现了一些专门化…

Overleaf Docker编译复现计划

Overleaf Docker编译复现计划 Overleaf Pro可以支持不同年份的Latex镜像自由选择编译&#xff0c;这实在是一个让人看了心痒痒的功能。但是很抱歉&#xff0c;这属于Pro付费功能。但是我研究了一下&#xff0c;发现其实和Docker编译相关的代码&#xff0c;社区版的很多代码都没…

使用Dockerfile构建镜像的详细指南

目录 前言 一、什么是 Dockerfile 二、使用 Dockerfile 定制镜像 开始构建镜像 上下文路径 三、指令详解 四、构建阿里云仓库 前言 Docker是一种流行的容器化平台&#xff0c;可以帮助开发人员和运维团队更轻松地构建、发布和运行应用程序。在Docker中&#xff0c;镜像是…

捷捷微电突发涨价函,Trench MOS产品线上调5%-10% | 百能云芯

近日&#xff0c;国产功率半导体厂商捷捷微电发布了一份《价格调整函》&#xff0c;宣布自2024年1月15日起&#xff0c;将对公司Trench MOS产品线的单价进行上调&#xff0c;上调幅度为5%-10%。 据悉&#xff0c;调整前已下的订单将继续按照原有单价和数量履行&#xff0c;而新…

java实现AES256对称加解密工具类

一、引入依赖包 引入相关依赖包 <dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15on</artifactId><version>1.70</version> </dependency> <!--lombok用于简化实体类开发--> <dependency&g…

Unity寻路A星算法

文章目录 实现步骤概览&#xff1a; 计算移动成本1. **定义移动成本函数**&#xff1a;2. **考虑不同类型的格子**&#xff1a;3. **动态调整成本**&#xff1a;4. **实际应用**&#xff1a; 优先级队列1. **初始化**&#xff1a;2. **节点评估**&#xff1a;3. **更新节点状态…

spring boot学习第八篇:通过spring boot、jedis实现秒单

参考&#xff1a;Redis实现分布式锁的7种方案 - 知乎 1、 准备数据库表&#xff0c;如下SQL表示库存表&#xff0c;有主键ID和库存数量字段 CREATE TABLE t_stock (id bigint(20) NOT NULL AUTO_INCREMENT,quantity bigint(20) NOT NULL,PRIMARY KEY (id) ) ENGINEInnoDB DEF…

未来气膜体育馆的发展趋势是什么?

未来气膜体育馆的发展趋势是多方面的&#xff0c;以下是其中几个方面的趋势。 起初&#xff0c;随着人们对体育运动的需求不断增加&#xff0c;气膜体育馆的建设和使用将成为一种趋势。气膜体育馆具有灵活性和可移动性的特点&#xff0c;可以快速搭建和拆除&#xff0c;能够适…

TOP 10 屏幕录制软件工具,可帮您轻松录制视频!

随着越来越多的人远程工作和学习&#xff0c;对可靠、高效的屏幕录制工具的需求变得越来越重要。屏幕录制已成为电子学习、游戏和视频创作的重要组成部分。然而&#xff0c;有这么多可用的屏幕录制工具&#xff0c;选择合适的工具可能具有挑战性。为了帮助您节省搜索时间和精力…

案例127:基于微信小程序的预约挂号系统

文末获取源码 开发语言&#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 小程序…

如何去开发直播电商系统小程序

明确你的直播电商系统的功能和特性&#xff0c;包括用户注册、商品展示、购物车、支付结算、直播功能、评论互动等。根据需求确定系统的基本架构和主要模块。 技术选型&#xff1a;选择适合你的直播电商系统的技术栈。考虑前端框架&#xff08;如React、Vue.js&#xff09;、后…

基于等效消耗最小(ECMS)的电氢综合能源系统能量管理策略Simulink模型

0. 前言 常见的EMS控制策略为基于状态机&#xff08;State Machine Control&#xff09;、基于等效消耗最小&#xff08;Equivalent Consumption Minimization Strategy&#xff0c;ECMS&#xff09;及调度控制模式。本文着重介绍前两种&#xff0c;针对第一种控制策略可参考模…