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

【LeetCode 热题 100】105. 从前序与中序遍历序列构造二叉树——(解法一)O(n^2)

文章目录

  • 整体思路
  • 完整代码
  • 时空复杂度
    • 时间复杂度:O(N)
    • 空间复杂度:O(N)

整体思路

这段代码同样旨在解决 “从前序与中序遍历序列构造二叉树” 的问题。与上一个通过复制子数组的解法相比,此版本采用了哈希表指针/索引来界定子数组范围,从而极大地优化了时空效率,是解决此问题的标准最优解法。

算法的核心思想依然是基于前序和中序遍历性质的分治法,但实现细节完全不同:

  1. 预处理:哈希表加速查找

    • 算法首先创建一个哈希表 index,用于存储中序遍历中每个元素值及其对应的索引。
    • 目的:这一步是性能优化的关键。它将“在中序遍历中查找根节点位置”这个操作的时间复杂度从 O(N) 降到了 O(1)
  2. 通过索引定义子问题

    • 算法不再通过 Arrays.copyOfRange 来创建新的子数组,而是定义了一个辅助的 dfs 函数,它接收多个索引参数来界定当前需要处理的子数组范围。
    • dfs(preorder, preL, preR, inL, inR, index):
      • preL, preR: 定义了当前子树在前序遍历数组 preorder 中的范围 [preL, preR) (左闭右开)。
      • inL, inR: 定义了当前子树在中序遍历数组 inorder 中的范围 [inL, inR)
    • 这种方式避免了任何数组复制,所有操作都在原始数组上进行。
  3. 递归构造逻辑

    • dfs 函数的执行流程如下:
      a. 基线条件if (preL == preR),如果范围为空,说明子树为空,返回 null
      b. 确定根节点:当前子树的根节点值是 preorder[preL]
      c. 分割子树 (O(1) 操作)
      • 使用预处理好的哈希表 index.get(preorder[preL]),瞬间(O(1)时间)找到根节点在中序遍历中的位置,记为 inRootIndex
      • 计算左子树的大小 leftSize = inRootIndex - inL
        d. 递归构建左右子树
      • 左子树
        * 前序范围是 [preL + 1, preL + 1 + leftSize)
        * 中序范围是 [inL, inL + leftSize)
        * 递归调用 dfs(...) 构建左子节点 left
      • 右子树
        * 前序范围是 [preL + 1 + leftSize, preR)
        * 中序范围是 [inL + 1 + leftSize, inR)
        * 递归调用 dfs(...) 构建右子节点 right
        e. 合并结果:创建新的 TreeNode,连接上一步构造出的 leftright 子节点,并返回。

通过哈希表和索引范围,该算法将每一步递归中的核心操作都优化到了 O(1),从而实现了整体的线性时间复杂度。

完整代码

class Solution {/*** 根据前序遍历和中序遍历的结果,构造二叉树(优化版)。* @param preorder 前序遍历数组* @param inorder  中序遍历数组* @return 构造出的二叉树的根节点*/public TreeNode buildTree(int[] preorder, int[] inorder) {int n = preorder.length;// 步骤 1: 预处理,用哈希表存储中序遍历中值到索引的映射,以实现 O(1) 查找。Map<Integer, Integer> index = new HashMap<>(n);for (int i = 0; i < n; i++) {index.put(inorder[i], i);}// 调用辅助的 DFS 函数开始递归构建return dfs(preorder, 0, n, 0, n, index);}/*** 辅助 DFS 函数,通过索引范围在原数组上构建子树。* @param preorder 完整的前序遍历数组* @param preL     当前子树在前序数组中的左边界(包含)* @param preR     当前子树在前序数组中的右边界(不包含)* @param inL      当前子树在中序数组中的左边界(包含)* @param inR      当前子树在中序数组中的右边界(不包含)* @param index    中序遍历值到索引的映射哈希表* @return 构建好的子树的根节点*/private TreeNode dfs(int[] preorder, int preL, int preR, int inL, int inR, Map<Integer, Integer> index) {// 递归的基线条件:如果范围为空,则子树为空。if (preL == preR) {return null;}// 根节点的值是前序遍历范围的第一个元素int rootVal = preorder[preL];// O(1) 时间从中序哈希表中获取根节点的位置int inRootIndex = index.get(rootVal);// 计算左子树的大小int leftSize = inRootIndex - inL;// 递归构建左子树// 左子树的前序范围:[preL + 1, preL + 1 + leftSize)// 左子树的中序范围:[inL, inRootIndex)  (即 inL + leftSize)TreeNode left = dfs(preorder, preL + 1, preL + 1 + leftSize, inL, inRootIndex, index);// 递归构建右子树// 右子树的前序范围:[preL + 1 + leftSize, preR)// 右子树的中序范围:[inRootIndex + 1, inR)TreeNode right = dfs(preorder, preL + 1 + leftSize, preR, inRootIndex + 1, inR, index);// 创建根节点,连接左右子树,并返回return new TreeNode(rootVal, left, right);}
}

时空复杂度

时间复杂度:O(N)

  1. 哈希表预处理for 循环遍历 inorder 数组一次,构建哈希表。时间复杂度为 O(N)
  2. 递归构建dfs 函数会对 preorder 数组中的每个元素处理一次(作为一次根节点)。
    • dfs 函数内部,所有的操作——哈希表查找、算术运算、创建新节点——都是 O(1) 的。
    • 由于每个节点只被访问一次,总的递归构建时间为 N * O(1) = O(N)

综合分析
总时间复杂度 = 预处理时间 + 递归构建时间 = O(N) + O(N) = O(N)

空间复杂度:O(N)

  1. 哈希表:算法创建了一个哈希表 index 来存储中序遍历的映射。在最坏情况下,所有元素都不同,哈希表需要存储 N 个键值对。因此,哈希表占用的空间为 O(N)
  2. 递归调用栈:递归的深度取决于树的高度 H
    • 在最好的情况下(一个完全二叉树),H 约为 log N,递归栈空间为 O(log N)。
    • 在最坏的情况下(一个极度倾斜的链状树),H 等于 N,递归栈空间为 O(N)

综合分析
算法所需的额外空间由哈希表和递归调用栈共同决定。总空间复杂度为 O(N) (哈希表) + O(H) (递归栈)。由于 H 的上界是 N,因此最坏情况下的总空间复杂度为 O(N) + O(N) = O(N)

参考灵神

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

完美卸载 Ubuntu 双系统:从规划到实施的完整指南

&#x1f4d6; 前言 最近成功完成了一次 Ubuntu 双系统的完整卸载&#xff0c;从最初的分区删除到最终解决 GRUB 引导问题&#xff0c;整个过程虽然有些曲折&#xff0c;但最终完美解决。本文将详细分享整个卸载过程&#xff0c;希望能帮助到有类似需求的朋友。 &#x1f3af…

深入理解oracle ADG和RAC

1. 引言 本节详细介绍oracle ADG和RAC。当然这里讲得的详细是相对理论的深入&#xff0c;不涉及到实验&#xff0c;比如ADG和RAC的搭建及调优等。 RAC (Real Application Clusters) 和 ADG (Active Data Guard)是Oracle 的两大核心高可用和灾备技术。它们是 Oracle 数据库高可用…

网络安全实践:从环境搭建到漏洞复现

要求&#xff1a;1.搭建docker2.使用小皮面板搭建pikachu靶场3.使用BP的爆破模块破解pikachu的登陆密码步骤4.Kail的msf复现永恒之蓝一.搭建docker1. Docker介绍Docker 是容器&#xff0c;可以部分完全封闭。封闭意味&#xff1a;一个物质&#xff08;放到容器&#xff09;&…

车载诊断架构 --- 诊断功能开发流程

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 做到欲望极简,了解自己的真实欲望,不受外在潮流的影响,不盲从,不跟风。把自己的精力全部用在自己。一是去掉多余,凡事找规律,基础是诚信;二是…

mysql数据库知识

MySQL数据库详解MySQL是目前全球最流行的关系型数据库管理系统之一&#xff0c;以其开源免费、高效稳定、易于扩展等特点&#xff0c;被广泛应用于Web开发、企业级应用等场景。本文将从基础概念、核心特性到实际应用&#xff0c;对MySQL进行全面解析。一、MySQL的基本概念1. 关…

基于springboot的美食文化和旅游推广系统

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业多年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了多年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;没有什么华丽的语言&#xff0…

Rust赋能文心大模型4.5智能开发

文心大模型4.5版本概论 文心大模型4.5是百度推出的最新一代大规模预训练语言模型,属于文心大模型(ERNIE)系列。该模型在自然语言处理(NLP)、多模态理解与生成等领域表现出色,广泛应用于智能搜索、内容创作、对话交互等场景。 核心能力 语言理解与生成 支持复杂语义理解…

前端抓包(不启动前端项目就能进行后端调试)--whistle

1、安装 1.1.安装node.js 1.2.安装whistle npm install -g whistle2.安装浏览器插件【SwitchyOmega】在谷歌浏览器应用商店下载安装即可配置proxy127.0.0.1:8989是w2 start的端口号启用代理3.启动服务&#xff08;每次抓包都得启动&#xff09; w2 start点击链接访问网页 http:…

kettle从入门到精通 第102课 ETL之kettle xxl-job调度kettle的两种方式

之前我们一起学习过xxl-job调度carte&#xff0c;采用的xxl-job执行器方式&#xff0c;不了解的可以查看《kettle从入门到精通 第六十一课 ETL之kettle 任务调度器&#xff0c;轻松使用xxl-job调用kettle中的job和trans 》 今天我们一起来学习下使用xxl-job直接使用http调用…

纯前端 JavaScript 实现数据导出到 CSV 格式

日常开发中&#xff0c;数据导出到文件通常有两种方式&#xff1a; 在后端处理&#xff0c;以文件流或者资源路径的方式返回&#xff1b;后端返回数据&#xff0c;前端按需处理后再触发浏览器的下载事件&#xff0c;已保存到本地文件。 这里介绍后者的一种零依赖的实现方式。…

香港理工大学实验室定时预约

香港理工大学实验室定时预约 文章目录香港理工大学实验室定时预约简介接单价格软件界面网站预约界面代码对爬虫、逆向感兴趣的同学可以查看文章&#xff0c;一对一小班教学(系统理论和实战教程)、提供接单兼职渠道&#xff1a;https://blog.csdn.net/weixin_35770067/article/d…

Spring AI 项目实战(十七):Spring Boot + AI + 通义千问星辰航空智能机票预订系统(附完整源码)

系列文章 序号文章名称1Spring AI 项目实战(一):Spring AI 核心模块入门2Spring AI 项目实战(二):Spring Boot + AI + DeepSeek 深度实战(附完整源码)3Spring AI 项目实战(三):Spring Boot + AI + DeepSeek 打造智能客服系统(附完整源码)4

STM32CubeMX+CLion 使用ARM_CMSIS_DSP

安装 参考&#xff1a; 【CLion开发stm32】如何使用DSP库 - 未知的奇迹 - 博客园 实际上这样配置会出一点小问题&#xff0c;现对其修改 1. 项目根目录下新建 DSP_LIB文件夹 将目录STM32CubeMX\Repository\STM32Cube_FW_G4_V1.6.1\Drivers\CMSIS\DSP下的Include文件夹和So…

深入解析C#接口实现的两种核心技术:派生继承 vs 显式实现

—— 如何优雅解决多接口冲突问题 &#x1f50d; 核心概念速览 派生成员实现 类通过继承基类方法隐式满足接口实现需求 interface IIfc1 { void PrintOut(string s); }class MyBaseClass { // 基类实现方法 public void PrintOut(string s) > Console.WriteLine($"Cal…

鸿蒙项目构建配置

鸿蒙项目构建配置 参考文档 深入鸿蒙开发之后&#xff0c;一般会遇到以下几个问题。 每次编译的时候需要手动配置不同的 versionCode 和 versionName&#xff1b;在使用 git 管理代码的时候&#xff0c;不同的人或者不在同一台电脑上&#xff0c;dev eco 这个编译器需要经常…

os.machine()详解

核心功能返回硬件架构 返回字符串表示系统的硬件架构&#xff0c;常见值包括&#xff1a; x86_64&#xff1a;64 位 x86 架构&#xff08;Intel/AMD&#xff09;armv7l&#xff1a;32 位 ARM 架构&#xff08;如树莓派 3B&#xff09;aarch64&#xff1a;64 位 ARM 架构&#x…

linux-shell脚本

linux-shell脚本一、什么是shell脚本&#xff1f;二、为什么要学习shell脚本&#xff1f;三、脚本执行的方式3.1 bash test.sh3.2 ./test.sh3.3 source test.sh3.4 . test.sh四、变量的使用4.1 变量定义与使用4.2 避免变量混淆4.3 位置变量for循环和位置变量的结合案例4.4 read…

【嵌入式】51单片机学习笔记-Keil5软件安装教程

00. 目录 文章目录00. 目录01. Keil C51概述02. Keil C51下载03. Keil C51安装04. Keil C51注册05. 附录01. Keil C51概述 Keil C51 是德国Keil公司&#xff08;现被ARM收购&#xff09;开发的嵌入式开发工具&#xff0c;专注于8051单片机的C语言和汇编开发。它是μVision IDE…

ai之 ubuntu本地安装mineru2.1.0

MinerU 目录 一、更新内容概述写在前面的话:总体来看,2.0版本升级为全新的 VLM 解析模式,更优于以前的基础解析方式。二、MinerU 安装部署下面使用源码来进行环境安装。注意:当前状态说明推荐解决方案如果是下载插件慢可以 指定阿里源三、MinerU 使用1. 在线体验2. 命令行使…

华为昇腾NPU与NVIDIA CUDA生态兼容层开发实录:手写算子自动转换工具链(AST级代码迁移方案)

点击 “AladdinEdu&#xff0c;同学们用得起的【H卡】算力平台”&#xff0c;H卡级别算力&#xff0c;按量计费&#xff0c;灵活弹性&#xff0c;顶级配置&#xff0c;学生专属优惠。 当国产AI芯片崛起遭遇生态壁垒&#xff0c;如何实现CUDA算子到昇腾平台的无损迁移成为关键挑…