力扣日记12.13-【二叉树篇】从中序与后序遍历序列构造二叉树

力扣日记:【二叉树篇】从中序与后序遍历序列构造二叉树

日期:2023.12.13
参考:代码随想录、力扣

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

题目描述

难度:中等

给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。

示例 1:
在这里插入图片描述

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

示例 2:

输入:inorder = [-1], postorder = [-1]
输出:[-1]

提示:

  • 1 <= inorder.length <= 3000
  • postorder.length == inorder.length
  • -3000 <= inorder[i], postorder[i] <= 3000
  • inorder 和 postorder 都由 不同 的值组成
  • postorder 中每一个值都在 inorder 中
  • inorder 保证是树的中序遍历
  • postorder 保证是树的后序遍历

题解

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
# define SOLUTION 2
public:
# if SOLUTION == 1
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        // 递归返回值与参数:返回值为中节点, 参数为中序和后序数组
        // 从中序与后序遍历序列构造二叉树的步骤
        // 1. 如果后序数组为空, 则为空节点(终止条件)
        if (postorder.size() == 0) return nullptr;  // 为空节点
        // 2. 根据后序数组最后一个值得到中节点值
        int nodeVal = postorder[postorder.size() - 1];  // 后序数组最后一个值为中节点值
        TreeNode* node = new TreeNode(nodeVal); // 构造中节点
        // 注意如果是叶子节点, 则不需要再去切割, 直接返回当前节点
        if (postorder.size() == 1) return node;
        // 3. 寻找中序数组位置作切割点
        int index = 0;
        for (int i = 0; i < inorder.size(); i++) {
            if (inorder[i] == nodeVal) {
                index = i;  
                // index 为中节点值对应序号, 则[0, index)为左子树, [index + 1, size)为右子树, 注意统一区间开闭 
            }
        }
        // 4. 根据此切割点对中序数组进行切割
        vector<int> inorderLeft(inorder.begin(), inorder.begin() + index);  // [0, index)
        vector<int> inorderRight(inorder.begin() + index + 1, inorder.end());   // [index + 1, size)
        // 5. 再根据切割后中序数组左右区间长度对后序数组进行切割
        vector<int> postorderLeft(postorder.begin(), postorder.begin() + inorderLeft.size()); // [0, left.size)
        vector<int> postorderRight(postorder.begin() + inorderLeft.size(), postorder.begin() + inorderLeft.size() + inorderRight.size()); // [left.size, left.size + right.size)
        // 6. 将中序数组与后序数组的左子树区间进行递归处理, 递归返回值为左子树的根节点,作为当前node左节点
        node->left = buildTree(inorderLeft, postorderLeft);   // 中序和后序的左子树遍历数组都分别按中序和后序遍历
        // 7. 将中序数组与后序数组的右子树区间进行递归处理
        node->right = buildTree(inorderRight, postorderRight);
        return node;    // 返回已经接上左右节点的中节点
    }
# elif SOLUTION == 2    // 优化:用下标索引,不需要每次构建子数组
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        int inorderBegin = 0, inorderEnd = inorder.size();  // [0, size)
        int postorderBegin = 0, postorderEnd = postorder.size();    // [0, size)
        return traversal(inorder, inorderBegin, inorderEnd, postorder, postorderBegin, postorderEnd);
    }
    TreeNode* traversal (vector<int>& inorder, int inorderBegin, int inorderEnd, vector<int>& postorder, int postorderBegin, int postorderEnd) {
        // 递归返回值与参数:返回值为中节点, 参数为原始中序后序数组, 以及用来表示当前中后序数组的下标
        // 不变量:左闭右开
        // 从中序与后序遍历序列构造二叉树的步骤
        // 1. 如果当前后序数组为空, 则为空节点(终止条件)
        if (postorderEnd - postorderBegin == 0) return nullptr;  // 为空节点 [Begin, Begin)
        // 2. 根据后序数组最后一个值得到中节点值
        int nodeVal = postorder[postorderEnd - 1];  // 后序数组最后一个值为中节点值, 右开, 则需-1
        TreeNode* node = new TreeNode(nodeVal); // 构造中节点
        // 注意如果是叶子节点, 则不需要再去切割, 直接返回当前节点
        if (postorderEnd - postorderBegin == 1) return node;
        // 3. 寻找中序数组位置作切割点
        int index = 0;
        for (int i = inorderBegin; i < inorderEnd; i++) {
            if (inorder[i] == nodeVal) {
                index = i;  
                // index 为中节点值对应序号, 则[inorderBegin, index)为左子树, [index + 1, inorderEnd)为右子树, 注意统一区间开闭 
            }
        }
        // 4. 根据此切割点对中序数组进行切割
        int inorderLeftBegin = inorderBegin; // 左子树区间的开始下标(左闭)
        int inorderLeftEnd = index; // 左子树区间的结束下标(右开)
        int inorderRightBegin = index + 1;  // 右子树区间
        int inorderRightEnd = inorderEnd;
        // 5. 再根据切割后中序数组左右区间长度对后序数组进行切割
        int LeftSize = inorderLeftEnd - inorderLeftBegin;    // 左子树大小
        int postorderLeftBegin = postorderBegin;
        int postorderLeftEnd = postorderBegin + LeftSize;    // 后序与中序的左子树数组大小一致, LeftSize: [postorderLeftBegin, postorderLeftBegin + LeftSize)
        int RightSize = inorderRightEnd - inorderRightBegin;
        int postorderRightBegin = postorderLeftEnd; // 左闭
        int postorderRightEnd = postorderLeftEnd + RightSize;
        // 6. 将中序数组与后序数组的左子树区间进行递归处理, 递归返回值为左子树的根节点,作为当前node左节点
        node->left = traversal(inorder, inorderLeftBegin, inorderLeftEnd, postorder, postorderLeftBegin, postorderLeftEnd);   // 中序和后序的左子树遍历数组都分别按中序和后序遍历
        // 7. 将中序数组与后序数组的右子树区间进行递归处理
        node->right = traversal(inorder, inorderRightBegin, inorderRightEnd, postorder, postorderRightBegin, postorderRightEnd);
        return node;    // 返回已经接上左右节点的中节点
    }
# endif
};

复杂度

时间复杂度:
空间复杂度:

思路总结

  • 从中序与后序遍历序列构造二叉树的步骤
      1. 如果后序数组为空, 则为空节点
      2. 根据后序数组最后一个值得到中节点值
      3. 寻找中序数组位置作切割点
      4. 根据此切割点对中序数组进行切割
      5. 再根据切割后中序数组左右区间长度对后序数组进行切割
      6. 将中序数组与后序数组的左子树区间进行递归处理(两个左子树区间数组分别为左子树的中序数组和后序数组)
      7. 将中序数组与后序数组的右子树区间进行递归处理
  • 示意图
    在这里插入图片描述
  • 对于第二种解法,即用下标索引表示子数组(而不是直接构造子数组),要分别确定好左右子树的中序和后序数组的开始、结束下标。统一用左闭右开来表示。相对繁琐一些,但时间和空间复杂度更优化。

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

题目描述

难度:中等

给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

示例 1:
在这里插入图片描述

输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]

示例 2:

输入: preorder = [-1], inorder = [-1]
输出: [-1]

提示:

  • 1 <= preorder.length <= 3000
  • inorder.length == preorder.length
  • -3000 <= preorder[i], inorder[i] <= 3000
  • preorder 和 inorder 均 无重复 元素
  • inorder 均出现在 preorder
  • preorder 保证 为二叉树的前序遍历序列
  • inorder 保证 为二叉树的中序遍历序列

题解

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        // 1. 如果前序数组为空, 则为空节点
        if (preorder.size() == 0)   return nullptr;
        // 2. 根据前序数组第一个值得到中节点值
        int nodeVal = preorder[0];
        // 构造中节点
        TreeNode* node = new TreeNode(nodeVal);
        // 如果是叶子节点直接返回
        if (preorder.size() == 1)   return node;
        // 3. 寻找中序数组位置作切割点
        int index = 0;
        for (int i = 0; i < inorder.size(); i++) {
            if (inorder[i] == nodeVal) {
                index = i;
            }
        }
        // 4. 根据此切割点对中序数组进行切割
        vector<int> inorderLeft(inorder.begin(), inorder.begin() + index);
        vector<int> inorderRight(inorder.begin() + index + 1, inorder.end());
        // 5. 再根据切割后的中序数组左右区间长度对前序数组进行切割
        vector<int> preorderLeft(preorder.begin() + 1, preorder.begin() + 1 + inorderLeft.size());  // 第二个值开始
        vector<int> preorderRight(preorder.begin() + 1 + inorderLeft.size(), preorder.end());
        // 6. 将中序数组与前序数组的左子树区间进行递归处理(两个左子树区间数组分别为左子树的中序数组和前序数组)
        node->left = buildTree(preorderLeft, inorderLeft);
        // 7. 将中序数组与前序数组的右子树区间进行递归处理
        node->right = buildTree(preorderRight, inorderRight);
        return node;
    }
};

复杂度

思路总结

  • 与 从后序与中序遍历序列构造二叉树 的思路基本一致
  • 从前序与中序遍历序列构造二叉树的步骤(前序:中左右;中序:左中右)
      1. 如果前序数组为空, 则为空节点
      2. 根据前序数组第一个值得到中节点值
      3. 寻找中序数组位置作切割点
      4. 根据此切割点对中序数组进行切割
      5. 再根据切割后的中序数组左右区间长度对前序数组进行切割
      6. 将中序数组与前序数组的左子树区间进行递归处理(两个左子树区间数组分别为左子树的中序数组和前序数组)
      7. 将中序数组与前序数组的右子树区间进行递归处理
  • 这里要明确一点:前序和中序、后序和中序都可以分别唯一确定一棵二叉树;但前序和后序不能唯一确定一棵二叉树!因为没有中序遍历无法确定左右部分,也就是无法分割。
    • 在这里插入图片描述
    • 上面两棵树的前序、后序都分别相等

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

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

相关文章

Prometheus

Prometheus [系统性能优化实践]JVM进阶实战之监控工具(Prometheus) https://www.cnblogs.com/johnnyzen/p/17388354.html ubuntu 22.04 配置 Prometheus 和 Grafana 服务器监控 https://blog.csdn.net/nvd11/article/details/128030197 Prometheus 是一个开源的监控系统&…

深入理解网络 I/O:单 Group 混杂模式|多 Group 主从模式

&#x1f52d; 嗨&#xff0c;您好 &#x1f44b; 我是 vnjohn&#xff0c;在互联网企业担任 Java 开发&#xff0c;CSDN 优质创作者 &#x1f4d6; 推荐专栏&#xff1a;Spring、MySQL、Nacos、Java&#xff0c;后续其他专栏会持续优化更新迭代 &#x1f332;文章所在专栏&…

Threejs漫天多彩粒子天空--粒子系统打造

一、导语 漫天多彩粒子天空特效应该也是Threejs项目中挺常见的一个需求&#xff0c;因为它是基于粒子系统&#xff0c;可以衍生出许多的不一样的方案&#xff0c;比如&#xff0c;星空特效&#xff0c;下雨特效&#xff0c;飘雪特效等等&#xff0c;不仅可以用在项目中增加氛围…

二叉搜索树的实现

本文旨在讲解如何编写一颗二叉搜索树&#xff0c;包括基本的增删查改的操作。 目录 一、二叉搜索树的概念 ​编辑二、二叉搜索树的编写 2.1节点的编写 2.2节点的插入 2.3节点的查找 2.4节点的删除 三、二叉搜索树的应用 四、 二叉搜索树的性能分析 五、完整代码 一、…

CD8+T细胞通过NKG2D-NKG2DL轴维持对MHC-I阴性肿瘤细胞的杀伤

今天给同学们分享一篇实验文章“CD8 T cells maintain killing of MHC-I-negative tumor cells through the NKG2D-NKG2DL axis”&#xff0c;这篇文章发表在Nat Cancer期刊上&#xff0c;影响因子为22.7。 结果解读&#xff1a; MHC-I阴性肿瘤的免疫疗法需要CD8 T细胞 作者先…

现代雷达车载应用——第2章 汽车雷达系统原理 2.2节 汽车雷达架构

经典著作&#xff0c;值得一读&#xff0c;英文原版下载链接【免费】ModernRadarforAutomotiveApplications资源-CSDN文库。 2.2 汽车雷达架构 从顶层来看&#xff0c;基本的汽车雷达由发射器&#xff0c;接收器和天线组成。图2.2给出了一种简化的单通道连续波雷达结构[2]。这…

什么是网络丢包以及如何解决

丢包的概念一直是网络行业争论的话题&#xff0c;在设计和实现网络时&#xff0c;它始终是考虑的关键因素&#xff0c;其重要性在于它对网络和网络系统的效率和整体性能的直接影响&#xff0c;即使是单个故障设备或配置错误的设置也会导致数据包丢失&#xff0c;也会严重影响整…

2 Mycat2 安装与启动

1、制作安装包 Mycat2不提供安装包,只提供核心JAR包,JAR包可以独立运行,安装包是使用Java Service Wrapper做壳的,如果需要安装包&#xff0c;需要自己制作。JAR可以作为Java库引入自己业务项目中使用Mycat2中的各个组件的设计都是可以独立使用的。 步骤如下&#xff1a; 1.…

【C++干货铺】继承后的多态 | 抽象类

个人主页点击直达&#xff1a;小白不是程序媛 C系列专栏&#xff1a;C干货铺 代码仓库&#xff1a;Gitee 目录 多态的概念 多态的定义和实现 多态的定义条件 虚函数 虚函数的重写 特殊情况 协变&#xff08;基类和派生类的虚函数返回值不同&#xff09; 析构函数的重…

ffmpeg踩坑之手动编译报错Unrecognized option ‘preset‘及rtsp/rtmp推流

本文解决的问题记录&#xff1a; 报错1&#xff1a;Unrecognized option preset. Error splitting the argument list: Option not found 报错2&#xff1a;ERROR: x264 not found using pkg-config 报错3&#xff1a;ffmpeg: error while loading shared libraries: libavd…

【linux】Debian不能运行sudo的解决

一、问题&#xff1a; sudo: 没有找到有效的 sudoers 资源&#xff0c;退出 sudo: 初始化审计插件 sudoers_audit 出错 二、可用的方法&#xff1a; 出现 "sudo: 没有找到有效的 sudoers 资源&#xff0c;退出" 和 "sudo: 初始化审计插件 sudoers_audit 出错&q…

spring面试:一、面试题分类总览+bean线程安全问题+AOP相关问题(定义、使用步骤、编程式事务管理和声明式事务管理和声明式事务管理失效)

面试题分类总览 bean线程安全问题 单例/多例 单例&#xff08;singleton&#xff09;&#xff1a;在每个spring ioc容器中都只有一个实例。 多例&#xff08;prototype&#xff09;&#xff1a;在每个spring ioc容器中有多个实例。 默认情况下spring中的bean都是单例的。但是…

基于Java SSM框架实现智能停车场系统项目【项目源码+论文说明】计算机毕业设计

基于java的SSM框架实现智能停车场系统演示 摘要 本论文主要论述了如何使用JAVA语言开发一个智能停车场管理系统&#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作者将论述…

call 和 apply:改变对象行为的秘密武器(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

TSINGSEE视频智能解决方案边缘AI智能与后端智能分析的区别与应用

视频监控与AI人工智能的结合是当今社会安全领域的重要发展趋势。随着科技的不断进步&#xff0c;视频监控系统已经不再局限于简单的录像和监视功能&#xff0c;而是开始融入人工智能技术&#xff0c;实现更加智能化的监控和安全管理。传统的监控系统往往需要人工操作来进行监控…

内网渗透测试基础——Windows PowerShell篇

内网渗透测试基础——Windows PowerShell篇 1. Windows PowerShell基础 Windows PowerShell是一种命令行外壳程序和脚本环境&#xff0c;它内置在每个受支持的Windows版本中&#xff08;Windows7、Windows Server 2008 R2及更高版本&#xff09;&#xff0c;为Windows命令行使…

讨好型人格最适合从事什么职业?

讨好型人格&#xff0c;其言行不是考虑个人&#xff0c;而是以满足对方为主&#xff0c;只要是他人的想法&#xff0c;都会尽力去满足&#xff0c;特别害怕自己做了什么事情&#xff0c;让对方产生不满的想法。遇到事情&#xff0c;也很难主动请求别人&#xff0c;总是依靠自己…

计算机组成原理-函数调用的汇编表示(call和ret指令 访问栈帧 切换栈帧 传递参数和返回值)

文章目录 call指令和ret指令高级语言的函数调用x86汇编语言的函数调用call ret指令小结其他问题 如何访问栈帧函数调用栈在内存中的位置标记栈帧范围&#xff1a;EBP ESP寄存器访问栈帧数据&#xff1a;push pop指令访问栈帧数据&#xff1a;mov指令小结 如何切换栈帧函数返回时…

APP安全测试填坑

在实习过程中&#xff0c;我接触到了一些SDL安全提测的工作。原来我是学web端渗透比较多的&#xff0c;移动端这块基本没怎么试过手&#xff0c;结果刚开始一直踩坑&#xff0c;连抓包都抓不到(&#xff34;▽&#xff34;)。 下面记录下我遇到的部分问题和解决方法&#xff0…

Python基础04-数据容器

零、文章目录 Python基础04-数据容器 1、了解字符串 &#xff08;1&#xff09;字符串的定义 字符串是 Python 中最常用的数据类型。我们一般使用引号来创建字符串。创建字符串很简单&#xff0c;只要为变量分配一个值即可。<class ‘str’>即为字符串类型。一对引号…