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

news2024/11/16 19:55:50

文章目录

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

题目

标题和出处

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

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

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

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

相关文章

7_1 tesseract 安装及使用

1、 安装tesseract   OCR&#xff0c;即Optical Character Recognition&#xff0c;光学字符识别&#xff0c;是指通过扫描字符&#xff0c;然后通过其形状将其翻译成电子文本的过程。对于图形验证码来说&#xff0c;它们都是一些不规则的字符&#xff0c;这些字符确实是由字…

运筹说 第99期 | 非线性规划—最优性条件

通过上期学习&#xff0c;大家已经了解了非线性规划中无约束极值问题及其求解方法。本期小编将为大家介绍最优性条件&#xff0c;包括可行下降方向、库恩-塔克条件等内容。 1 可行下降方向 起约束作用 假定X(0)是上述问题的一个可行解&#xff0c;满足所有约束。对某约束条件g…

2024年,如何打造惊艳的个人博客/出版系统并且赚点小钱?

几年前&#xff0c;我就推荐过用Markdown写作静态博客。静态博客几乎是零托管成本&#xff0c;比较适合个人博客起步。Markdown便于本地搜索&#xff0c;也可当作是个人知识库方案。 现在有了新的进展。我不仅构建了一个视觉上相当不错的个人网站&#xff0c;还美化了github、…

Linux/Frolic

Enumeration nmap 还是扫描系统对外开放的端口情况&#xff0c;对外开放了22,139,445,还有9999端口&#xff0c;显示是http服务&#xff0c;使用了nginx 1.10.3 ┌──(kali㉿kali)-[~/HTB/Frolic] └─$ nmap -sC -sV -oA nmap -Pn 10.10.10.111 Starting Nmap 7.93 ( http…

pandas进行数据计算时如何处理空值的问题?

目录 1.数据预览&#xff1a; 2.解决方法 &#xff08;1&#xff09;问题示例 &#xff08;2&#xff09;方法 A.方法一 B.方法二 1.数据预览&#xff1a; 2.解决方法 &#xff08;1&#xff09;问题示例 如下图如果不理睬这些空值的话&#xff0c;计算总分便也会是空值…

uni-app购物车页面详细代码

效果图&#xff1a; 这里的购物车加减用的是uni-app中的sku插件 代码附下&#xff08;全&#xff09;&#xff1a; <script setup lang"ts"> import {reqMemberCartList,reqMemberdelentCart,reqMemberPutCart,putMemberCartSelectedAPI, } from /services/…

linux后台进程的总结

文章目录 方案1 nohup &方案2 screen 方案1 nohup & 1、单独使用 nohup 执行脚本&#xff0c;如下图所示&#xff0c;终端会被接管&#xff0c;就是标准输入stdin 被关闭了&#xff0c;使用ctrlc会导致终止执行&#xff0c;但是可以关闭这个终端&#xff0c;重新打开终…

c5060:out can‘t be used with used with non-varying visibility

openGL系列文章目录 文章目录 openGL系列文章目录前言一、GLSL language integration是什么&#xff1f;二、GLSL language integration配置二、GLSL language integration编译报错解决 前言 GLSL插件下载和安装&#xff1a;GLSL language integration下载地址 你也可以在visu…

【python可视化大屏】使用python实现可拖拽数据可视化大屏

介绍&#xff1a; 我在前几期分享了关于爬取weibo评论的爬虫&#xff0c;同时也分享了如何去进行数据可视化的操作。但是之前的可视化都是单独的&#xff0c;没有办法在一个界面上展示的。这样一来呢&#xff0c;大家在看的时候其实是很不方便的&#xff0c;就是没有办法一目了…

软件测试|教你使用dataclass

前言 当我们需要在Python中定义一种简单的数据容器类时&#xff0c;dataclass是一个非常有用的工具。它允许我们轻松地创建具有一些自动化特性的类&#xff0c;例如自动生成__init__()、__repr__()和__eq__()等方法。本文将详细介绍dataclass的使用&#xff0c;并提供示例来说…

Qt点击按钮在其附近弹出一个窗口

效果 FS_PopupWidget.h #ifndef FS_POPUPWIDGET_H #define FS_POPUPWIDGET_H#pragma once#include <QToolButton> #include <QWidgetAction> #include <QPointer>class QMenu;class FS_PopupWidget : public QToolButton {Q_OBJECTpublic:FS_PopupWidget(QW…

3dmax有哪些技巧?3damx不为人知的秘密

在装修设计和建筑可视化领域&#xff0c;3D MAX是一款强大的工具&#xff0c;可以帮助我们创建出高质量的3D模型和动画。然而&#xff0c;要充分发挥其潜力&#xff0c;我们需要掌握一些3D MAX渲染的技巧。以下是一些实用的技巧&#xff0c;帮助你提高渲染效率和质量。 合理设…

【总结】Dinky学习笔记

概述 Dinky 是一个开箱即用、易扩展&#xff0c;以 Apache Flink 为基础&#xff0c;连接 OLAP 和数据湖等众多框架的一站式实时计算平台&#xff0c;致力于流批一体和湖仓一体的探索与实践 官网&#xff1a;Dinky 核心特性 沉浸式&#xff1a;提供专业的 DataStudio 功能&a…

mysql8 源码编译 客户端连接运行 报段异常解决

mysql8 源码编译 客户端连接运行 报段异常解决。解决方案&#xff1a;删除之前编译的文件。先安装libncurses-dev依赖&#xff0c;在重新编译。原因&#xff1a;第一次编译没有libncurses-dev依赖&#xff0c;编译告警&#xff0c;再次编译有缓存&#xff0c;没有引入声明头文件…

Umi3 创建,配置环境,路由传参(代码示例)

目录 创建项目 配置环境 创建脚手架 项目结构及其目录、 路由 配置路由 嵌套路由 编程式导航和声明式导航 声明式导航 编程式导航 约定式路由 路由传参 query传参&#xff08;问号&#xff09; 接收参数 params传参&#xff08;动态传参&#xff09; 接收参数 创…

[zabbix] zabbix监控

一、温习zabbix自定义监控 二、zabbix 自动发现与自动注册 2.1 zabbix 自动发现 //zabbix 自动发现&#xff08;对于 agent2 是被动模式&#xff09; zabbix server 主动的去发现所有的客户端&#xff0c;然后将客户端的信息登记在服务端上。 缺点是如果定义的网段中的主机数…

SpringBoot教程(五) | SpringBoot中Controller详解

SpringBoot教程(五) | SpringBoot中Controller详解 SpringBoot整合SpringMvc其实千面一直讲的都是。只需要我们在pom文件中引入 web的starter就可以了&#xff0c;然后我们就可以正常使用springMvc中的功能了。所以本篇文章可能更多的是回顾&#xff0c;回顾一下springMVC中的…

Centos7 制作系统镜像iso文件

Centos7 制作系统镜像iso文件 1. 系统备份镜像1.1 安装mondo1.2 制作备份镜像1.3 恢复系统 1. 系统备份镜像 这部分针对有系统备份需求&#xff0c;防止系统遭受意外状况&#xff0c;无法紧急恢复。整体备份内容较大&#xff0c;建议考虑后再进行操作 本次使用mondo软件进行备…

代码随想录算法训练营第28天 | 93.复原IP地址 78.子集 90.子集II

目录 93.复原IP地址 &#x1f4a1;解题思路 回溯三部曲 # 判断子串是否合法 &#x1f4bb;实现代码 78.子集 &#x1f4a1;解题思路 回溯三部曲 &#x1f4bb;实现代码 90.子集II &#x1f4a1;解题思路 &#x1f4bb;实现代码 93.复原IP地址 题目链接&#x…

Rocketmq rust版本-开篇

我是蚂蚁背大象(Apache EventMesh PMC&Committer)&#xff0c;文章对你有帮助给Rocketmq-rust star,关注我GitHub:mxsm&#xff0c;文章有不正确的地方请您斧正,创建ISSUE提交PR~谢谢! Emal:mxsmapache.com Rust重构Rocketmq,大家好我是mxsm(Apache EventMesh PMC&Comm…