【LeetCode热题100】打卡第30天:从前序遍历与中序遍历序列构造二叉树二叉树展开为链表

news2024/12/23 22:06:50

文章目录

  • 【LeetCode热题100】打卡第30天:从前序遍历与中序遍历序列构造二叉树&二叉树展开为链表
    • ⛅前言
  • 从前序与中序遍历构造二叉树
    • 🔒题目
    • 🔑题解
  • 从中序与后序遍历构造二叉树
    • 🔒题目
    • 🔑题解
  • 二叉树展开为链表
    • 🔒题目
    • 🔑题解

【LeetCode热题100】打卡第30天:从前序遍历与中序遍历序列构造二叉树&二叉树展开为链表

⛅前言

大家好,我是知识汲取者,欢迎来到我的LeetCode热题100刷题专栏!

精选 100 道力扣(LeetCode)上最热门的题目,适合初识算法与数据结构的新手和想要在短时间内高效提升的人,熟练掌握这 100 道题,你就已经具备了在代码世界通行的基本能力。在此专栏中,我们将会涵盖各种类型的算法题目,包括但不限于数组、链表、树、字典树、图、排序、搜索、动态规划等等,并会提供详细的解题思路以及Java代码实现。如果你也想刷题,不断提升自己,就请加入我们吧!QQ群号:827302436。我们共同监督打卡,一起学习,一起进步。

博客主页💖:知识汲取者的博客

LeetCode热题100专栏🚀:LeetCode热题100

Gitee地址📁:知识汲取者 (aghp) - Gitee.com

Github地址📁:Chinafrfq · GitHub

题目来源📢:LeetCode 热题 100 - 学习计划 - 力扣(LeetCode)全球极客挚爱的技术成长平台

PS:作者水平有限,如有错误或描述不当的地方,恳请及时告诉作者,作者将不胜感激

从前序与中序遍历构造二叉树

🔒题目

原题链接:105.从前序与中序遍历构造二叉树

image-20230630161601393

🔑题解

  • 解法一:递归

    解题大致思路:首先我们需要明确,前序遍历的顺序都是 $ 根节点→左节点→右节点$,中序遍历的顺序都是 $ 左节点→根节点→右节点$,这个特性是实现递归的关键,我们可以利用这个特性,①先通过前序序列得到当前层的根节点,②然后通过中序序列根据这个根节点,划分出左右子树,递归地进行①和②两步,最终就可以构造出一颗完整的二叉树

    示意图:

    image-20230630175109230

    image-20230630175959690

    具体过程分析如下所示:

    preorder = [3,9,20,15,7]
    inorder = [9,3,15,20,7]
    首先根据 preorder 找到根节点是 3
        
    然后根据根节点将 inorder 分成左子树和右子树
    左子树
    inorder [9]
    
    右子树
    inorder [15,20,7]
    
    把相应的前序遍历的数组也加进来
    左子树
    preorder[9] 
    inorder [9]
    
    右子树
    preorder[20 15 7] 
    inorder [15,20,7]
    
    现在我们只需要构造左子树和右子树即可,成功把大问题化成了小问题
    然后重复上边的步骤继续划分,直到 preorder 和 inorder 都为空,返回 null 即可
    

    这里有必要详细讲解一下左右区间划分的推导:

    • 左子树区间范围的推导:
      • preorder[1,index+1):确定左侧边界(左侧的1怎么来的),前序遍历,preorder[0]必定是根节点,所以左子树的节点必定是从1开始的;确定右侧边界(右侧的 index+1怎么来的),右侧边界需要依据左子树节点的数量计算得到,而中序遍历正好划分了左右子树,所以左子树的数量就是 index ,由于 我们是从1开始计算的,所以 index 要 +1
      • inorder[0,index):确定左侧边界(左侧的0怎么来的),中序遍历,index左侧全是左子树,所以从0开始;确定右侧边界(右侧的 index怎么来的),中序遍历 index 指向根节点,不能取,所以左侧是 index
    • 右子树区间范围的推导:
      • preorder[index+1,preorder.length):正好是上面区间的补集
      • inorder[index+1,inorder.length):同理,也是上面区间的补集,但是左边闭区间不能取 index,index是根节点
    class Solution {
        public TreeNode buildTree(int[] preorder, int[] inorder) {
            if (preorder.length == 0 || inorder.length == 0) {
                return null;
            }
            // 获取当前的根节点
            TreeNode root = new TreeNode(preorder[0]);
            // 遍历前序序列,找出在根节点在中序序列中的位置
            int index = 0;
            for (int i = 0; i < preorder.length; i++) {
                if (preorder[0] == inorder[i]) {
                    index = i;
                    break;
                }
            }
            // 构建左子树
            root.left = buildTree(Arrays.copyOfRange(preorder, 1, index + 1),
                    Arrays.copyOfRange(inorder, 0, index));
            // 构建右子树
            root.right = buildTree(Arrays.copyOfRange(preorder, index + 1, preorder.length),
                    Arrays.copyOfRange(inorder, index + 1, inorder.length));
            return root;
        }
    }
    

    复杂度分析:

    • 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn),需要递归 l o g n logn logn次,每次都需要走一遍循环,循环最坏情况为 n n n,最好情况为1
    • 空间复杂度: O ( l o g n ) O(logn) O(logn),递归 l o g n logn logn次,所需栈空间为 l o g n logn logn

    其中 n n n 为数组中元素的个数

    代码优化

    1. 数据结构上的优化:上面的代码,我们每次递归都需要执行一次for循环去中序数组中定位根节点的位置,这个操作执行一遍还好,但是递归执行就十分费时间,这里直接使用一个HashMap进行映射,从而减少时间消耗
    2. 划分方式上的优化:上面代码,是直接调用Arrays.copyOfRange方法进行左右子树的划分,每次都需要遍历一遍原数组,耗费性能较低,并且每次调用都会重新创建一个数组对象,耗费内存,我们使用指针的方式来划分左右子树,在原数组的基础上实现左右数组的划分,既不用遍历数组又不要新建对象,从而减少时间和内存消耗

    具体实现思路和上面是类似的,就是将上面比较直白的区间划分,改用指针来划分

    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @author ghp
     * @title
     */
    class Solution {
        private Map<Integer, Integer> map = new HashMap<>(16);
    
        public TreeNode buildTree(int[] preorder, int[] inorder) {
            if (preorder.length == 0 || inorder.length == 0) {
                return null;
            }
            // map用于映射节点的值与索引,方便快速定位
            for (int i = 0; i < inorder.length; i++) {
                map.put(inorder[i], i);
            }
            return buildTree(preorder, 0, preorder.length, inorder, 0, inorder.length);
        }
    
        /**
         * 构建二叉树
         *
         * @param preorder 当前根节点的前序遍历
         * @param preLeft  当前根节点前序遍历的左边界
         * @param preRight 当前根节点前序遍历的右边界
         * @param inorder  当前根节点的中序遍历
         * @param inoLeft  当前根节点中序遍历的左边界
         * @param inoRight 当前根节点中序遍历的右边界
         * @return
         */
        private TreeNode buildTree(int[] preorder, int preLeft, int preRight, 
                                   int[] inorder, int inoLeft, int inoRight) {
            if (preLeft == preRight) {
                return null;
            }
            TreeNode root = new TreeNode(preorder[preLeft]);
            // 定位当前根节点在中序遍历数组的具体位置
            int index = map.get(root.val);
            // 计算左子树节点的数量
            int leftNum = index - inoLeft;
            // 构建左子树
            root.left = buildTree(preorder, preLeft + 1, preLeft + leftNum + 1,
                    inorder, inoLeft, index);
            // 构建右子树
            root.right = buildTree(preorder, preLeft + leftNum + 1, preRight,
                    inorder, index + 1, inoRight);
            return root;
        }
    }
    

    复杂度分析:

    • 时间复杂度: O ( n ) O(n) O(n),遍历一遍数组,时间复杂度是 n n n,递归划分二叉树,时间复杂度是 l o g n logn logn,所以时间复杂度是 n + l o g n n+logn n+logn
    • 空间复杂度: O ( n ) O(n) O(n),需要开闭一个Map集合空间复杂度是 n n n,递归空复杂度是 l o g n , 也就是树的高度 logn,也就是树的高度 logn,也就是树的高度,所以空间复杂度是 n + l o g n n+logn n+logn

    其中 n n n 为数组中元素的个数

    代码优化:和上一题类似

    
    

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

🔒题目

原题链接:106.从前序与中序遍历构造二叉树

image-20230630161625283

🔑题解

  • 解法一:递归

    思路和上一题很类似,就是区间的划分有点不一样罢了

    • 左子树区间范围的推导:
      • inorder[0,index):中序遍历,index正好是根节点索引,index左侧是左子树,index右侧是右子树
      • postorder[0,index):确定左侧边界(左侧的1怎么来的),后序遍历,先左后右最后才是中间节点,所以左子树节点必定从 0 开始,边界就是 左子树的长度,也就是 index ,
    • 右子树区间范围的推导:
      • inorder[index+1,n):中序遍历,index正好是根节点索引,index左侧是左子树,index右侧是右子树
      • postorder[index,n-1):正好是上面区间的补集,但是需要排除最后一个节点,因为后序遍历最后一个节点是根节点
    class Solution {
        public TreeNode buildTree(int[] inorder, int[] postorder) {
            // 递归终止条件
            if (inorder.length == 0 || postorder.length == 0) {
                return null;
            }
            int n = postorder.length;
            // 后序遍历最后一个节点一定是根节点
            TreeNode root = new TreeNode(postorder[n - 1]);
            // 定位中序数组中的根节点
            int index = 0;
            for (int i = 0; i < inorder.length; i++) {
                if (inorder[i] == root.val){
                    index = i;
                    break;
                }
            }
            // 构建左子树
            root.left = buildTree(Arrays.copyOfRange(inorder, 0, index),
                    Arrays.copyOfRange(postorder, 0, index));
            // 构建左子树
            root.right = buildTree(Arrays.copyOfRange(inorder, index+1, n),
                    Arrays.copyOfRange(postorder, index, n-1));
            return root;
        }
    }
    

    复杂度分析:

    • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
    • 空间复杂度: O ( 1 ) O(1) O(1)

    其中 n n n 为数组中元素的个数

    代码优化:和上一题的优化是一摸一样的,这里就不作过多讲解了,不懂的可以回看上一题

    class Solution {
        private Map<Integer, Integer> map = new HashMap<>(16);
    
        public TreeNode buildTree(int[] inorder, int[] postorder) {
            if (inorder.length == 0 || postorder.length == 0) {
                return null;
            }
            // map用于节点的值与索引,方便快速定位
            for (int i = 0; i < inorder.length; i++) {
                map.put(inorder[i], i);
            }
            return buildTree(inorder, 0, inorder.length,
                    postorder, 0, postorder.length);
        }
    
        private TreeNode buildTree(int[] inorder, int inoLeft, int inoRight, int[] postorder, int posLeft, int posRight) {
            if (posLeft == posRight) {
                return null;
            }
            // 后序遍历最后一个节点一定是根节点
            TreeNode root = new TreeNode(postorder[posRight - 1]);
            // 定位中序数组中的根节点
            int index = map.get(root.val);
            // 计算左子树的长度
            int leftLen = index - inoLeft;
            // 构建左子树
            root.left = buildTree(inorder, inoLeft, index,
                    postorder, posLeft, posLeft + leftLen);
            // 构建右子树
            root.right = buildTree(inorder, index + 1, inoRight,
                    postorder, posLeft + leftLen, posRight - 1);
            return root;
        }
    }
    

二叉树展开为链表

🔒题目

原题链接:114.二叉树展开为链表

image-20230630231130425

🔑题解

  • 解法一:暴力

    
    

    复杂度分析:

    • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
    • 空间复杂度: O ( 1 ) O(1) O(1)

    其中 n n n 为数组中元素的个数

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

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

相关文章

使用 Maya Mari 设计 3D 波斯风格道具(p1)

今天瑞云渲染小编给大家带来了Simin Farrokh Ahmadi 分享的Persian Afternoon 项目过程&#xff0c;解释了 Maya 和 Mari 中的建模、纹理和照明过程。 介绍 我的名字是西敏-法罗赫-艾哈迈迪&#xff0c;人们都叫我辛巴 在我十几岁的时候&#xff0c;我就意识到我喜欢艺术和创造…

python最佳开发环境组合(pycharm+anaconda)

一、pycharmanaconda是python 最佳开发环境组合 1.pycharm与vscode对比 pycharm社区版与pycharm pro pycharm pro 与vscode 二、anaconda Anaconda Python 集成包 工具箱。 所以没有必要下载传统Python (cPython)个人十分不推荐使用传统python做科学计算&#xff0c; 一来…

【王道·操作系统】第五章 输入输出管理【未完】

一、I/O设备 1.1 I/O设备的基本概念 I/O&#xff0c;Input/Output&#xff1a;输入/输出I/O 设备&#xff1a;将数据输入到计算机&#xff0c;或者可以接收计算机输出数据的外部设备&#xff0c;属于计算机中的硬件部件UNIX系统将外部设备抽象为一种特殊的文件&#xff0c;用户…

C语言无类型指针 void* 学习

int * 类型的指针变量&#xff0c;只能保存int型的数据的地址&#xff1b; double * 类型的指针变量&#xff0c;只能保存double型的数据的地址&#xff1b; void 指针可以指向任意类型的数据&#xff0c;可以用任意类型的指针对 void 指针赋值&#xff1b; void 在英文中作为…

基于PyQt5的桌面图像调试仿真平台开发(3)黑电平处理

系列文章目录 基于PyQt5的桌面图像调试仿真平台开发(1)环境搭建 基于PyQt5的桌面图像调试仿真平台开发(2)UI设计和控件绑定 基于PyQt5的桌面图像调试仿真平台开发(3)黑电平处理 基于PyQt5的桌面图像调试仿真平台开发(4)白平衡处理 基于PyQt5的桌面图像调试仿真平台开发(5)…

【LeetCode】动态规划 刷题训练(七)

文章目录 918. 环形子数组的最大和题目解析状态转移方程f[i]状态转移方程g[i]状态转移方程 初始化返回值完整代码 152. 乘积最大子数组题目解析状态转移方程f[i]状态转移方程g[i]状态转移方程 初始化完整代码 1567. 乘积为正数的最长子数组长度题目解析状态转移方程f[i]状态转移…

前端-盒子模型

元素显示模式 块级 行内 行内块 外边距折叠现象 合并现象 塌陷现象 &#xff08;1&#xff09;合并现象 场景&#xff1a;垂直布局的块级元素&#xff0c;上下的 margin 会合并 结果&#xff1a;最终两者距离为 margin 的最大值 解决方法&#xff1a;只给其中一个盒子设置 …

u盘ntfs和fat32哪个好 把u盘改成ntfs有什么影响

u盘在日常生活中的使用频率很高&#xff0c;许多用户在选购u盘时很少会注意到u盘格式&#xff0c;但u盘的格式对u盘的使用有很大影响。u盘格式有很多&#xff0c;常见的有ntfs和fa32&#xff0c;u盘ntfs和fat32哪个好&#xff1f;这要看u盘的使用场景。把u盘改成ntfs有什么影响…

简要记录java 锁

Java中的锁机制主要分为Lock和Synchronized. Synchronized在JVM里的实现是基于进入和退出Monitor对象来实现方法同步和代码块同步的。monitorenter指令是在编译后插入到同步代码块的开始位置&#xff0c;而monitorexit是插入到方法结束处和异常处&#xff0c;JVM要保证每个mon…

datatable刷新数据,js不整体刷新页面,使用DataTables表格插件定时更新后台数据变化

文章目录 前言一、meta的http-equiv属性二、使用DataTables表格插件2.1.整体思路2.2.将$(#myTableId).DataTable({……}&#xff09;封装成函数2.3刷新表格数据函数2.4统一调用刷新表格的自动加载函数2.4定时间隔执行刷新自动加载函数 前言 最近遇到一个需求&#xff0c;需要刷…

【新版系统架构】第七章-系统架构设计基础知识(架构风格、复用)

软考-系统架构设计师知识点提炼-系统架构设计师教程&#xff08;第2版&#xff09; 第一章-绪论第二章-计算机系统基础知识&#xff08;一&#xff09;第二章-计算机系统基础知识&#xff08;二&#xff09;第三章-信息系统基础知识第四章-信息安全技术基础知识第五章-软件工程…

安装两个mysql

标题:安装两个mysql 参考blog&#xff1a;MySQL–修改mysql服务可执行文件的路径&#xff08;Windows&#xff09; 参考视频&#xff1a;mysql安装-安装多个mysql方法 安装第一个&#xff0c;网上有很多教程&#xff0c;这里就附上一个链接了&#xff1a;mysql5.5安装 安装第…

JS知识点汇总(七)--数据类型

1. JavaScript中的简单数据类型有哪些&#xff1f; 1、概述 JS 中有六种简单数据类型&#xff1a;undefined、null、boolean、string、number、symbol ES10中的新特性 BigInt (任意精度整数)&#xff0c;目前还处于stage-4阶段&#xff0c;不出意外即将成为js的第七种基本数据…

036:mapboxGL点击某位置,转换坐标为地址,弹出地理信息

第036个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+mapbox中点击某位置,转换坐标位地址,弹出地理信息. 直接复制下面的 vue+mapbox源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共106行)相关API参考:专栏目标示例效果 配置方式 1)…

完整的复数类

复数类应该具有的操作 运算&#xff1a;&#xff0c;-&#xff0c;*&#xff0c;/ 比较&#xff1a;&#xff0c;! 赋值&#xff1a; 求模&#xff1a;modulus 利用操作符重载 统一复数与实数的运算方式 统一复数与实数的比较方式 注意事项 C 规定赋值操作符 () 只能重载…

vue项目运行不起来,可能是版本等不兼容问题

给pakeage.json 运行脚本前加上&#xff1a;set NODE_OPTIONS--openssl-legacy-provider && 即可。

echarts 实现3D饼图

2023.6.30今天我学习了如何使用echarts渲染一个3d的饼图&#xff0c;效果如下&#xff1a; 相关代码如下&#xff1a; <template><div ref"pie3d"/> </template> <script>mounted() {this.myChart this.$echarts.init(this.$refs.pie3d);…

【AUTOSAR】BMS开发实际项目讲解(二十五)----电池管理系统安全状态过渡

安全状态过渡 关联的系统需求 TSR-BMS-S201、TSR-BMS-S202、TSR-BMS-S203、TSR-BMS-S204、TSR-BMS-S601、TSR-BMS-S602、TSR-BMS-S603、TSR-BMS-S604、TSR-BMS-S605、TSR-BMS-S606、TSR-BMS-S607、TSR-BMS-S608、TSR-BMS-S609、TSR-BMS-S610、TSR-BMS-S611、TSR-BMS-S612; TSC…

【Linux系统编程】—进程学习笔记(fork进程创建、退出、僵死进程与孤儿进程、如何避免僵死进程)

目录 一、进程关键概念 二、进程创建实战 1、fork函数 2、fork创建一个子进程的一般目的&#xff1a; 3、fork函数实例&#xff1a; 4、fork的写时拷贝技术&#xff08;COW&#xff09; 三、进程退出 1、正常退出 2、异常退出 3、总结 四、僵死进程与孤儿进程 1、什…

融合学习:跨文化交流的学习平台

在全球化的时代&#xff0c;跨文化交流已经成为了一个不可避免的现象。在这种情况下&#xff0c;融合学习平台成为了一个非常重要的工具&#xff0c;可以帮助人们更好地了解和学习不同文化之间的差异和相似之处。本文将探讨融合学习平台的重要性&#xff0c;以及如何选择最佳的…