从前序和中序遍历重建二叉树 —— C++ 递归 + 哈希表写法解析

从前序和中序遍历重建二叉树 —— C++ 递归 + 哈希表写法解析

一、题目背景

已知一棵二叉树的:

  • 前序遍历序列preorder
  • 中序遍历序列inorder

并且树中不存在重复节点值,要求 重建这棵二叉树,返回根节点 TreeNode*


二、关键性质回顾

二叉树的遍历有这些性质(这里用到前序 + 中序):

  1. 前序遍历(Preorder):
    顺序是:根节点 -> 左子树 -> 右子树
     所以 preorder[0] 一定是整棵树的 根节点
  2. 中序遍历(Inorder):
    顺序是:左子树 -> 根节点 -> 右子树
    在中序序列中,根节点左边的部分是“左子树”,右边的部分是“右子树”

因为题目保证所有节点值互不相同,所以我们可以用一个哈希表

unordered_map<int, int> pos; 

来把「节点值」映射到「它在中序遍历中的下标」,方便 O(1) 查找。


三、整体思路

  1. 预处理
    inorder 每个值所在的位置存进哈希表 pos,方便通过值快速找到它在中序序列中的下标。
  2. 前序的指针 preIndex
    因为前序遍历顺序是:根 → 左 → 右,我们可以维护一个全局下标 preIndex,每次从 preorder[preIndex] 取当前子树的根,然后 preIndex++,继续构造子树。
    • 如果 l > r,说明这个区间是空的,对应的子树为空,返回 nullptr
    • 否则:
      1. 取当前根:val = preorder[preIndex++]
      2. 在中序中找到根的位置:mid = pos[val]
      3. 左子树对应中序区间 [l, mid - 1]
      4. 右子树对应中序区间 [mid + 1, r]
      5. 递归生成左右子树,并挂到根上。

递归构造子树
用一个函数 dfs(l, r) 表示:

根据「中序遍历的区间 [l, r]」,构造出这一段对应的子树,并返回这棵子树的根节点指针。

四、代码实现解析

代码如下:

class Solution { public: TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) { int n = preorder.size(); unordered_map<int,int> pos; pos.reserve(n * 2); for (int i = 0; i < n; ++i) pos[inorder[i]] = i; int preIndex = 0; auto dfs = [&](auto&& self, int l, int r) -> TreeNode* { if (l > r) return nullptr; int val = preorder[preIndex++]; int mid = pos[val]; TreeNode* root = new TreeNode(val); root->left = self(self, l, mid - 1); root->right = self(self, mid + 1, r); return root; }; return dfs(dfs, 0, n - 1); } }; 

下面分块解释。


1. 建立中序下标哈希表
int n = preorder.size(); unordered_map<int,int> pos; pos.reserve(n * 2); for (int i = 0; i < n; ++i) pos[inorder[i]] = i; 
  • pos 的作用:
    pos[x] 表示「值为 x 的节点,在中序遍历中的位置」。
  • reserve(n * 2);
    提前为哈希表预留空间,减少扩容,略微优化性能。

2. 维护前序下标
int preIndex = 0; 
  • preIndex 表示当前该从 preorder 的哪个位置取根节点。
  • 每次 preorder[preIndex++],就像顺着前序遍历顺序不断往前走。

3. 用 lambda + Y 组合做递归
auto dfs = [&](auto&& self, int l, int r) -> TreeNode* { if (l > r) return nullptr; int val = preorder[preIndex++]; int mid = pos[val]; TreeNode* root = new TreeNode(val); root->left = self(self, l, mid - 1); root->right = self(self, mid + 1, r); return root; }; 
  • 这里 dfs 是一个 泛型 lambda,通过参数 self 自己调用自己,实现递归。
  • 参数含义:
    • self:指向自身的可调用对象,用于递归调用。
    • l, r:当前子树对应的 中序遍历区间[l, r]
  • 递归逻辑:
    1. 边界if (l > r) return nullptr;
      中序区间为空,对应的子树为空。

返回当前根:

return root; 

递归构造右子树:

root->right = self(self, mid + 1, r); 

递归构造左子树:

root->left = self(self, l, mid - 1); 

构造当前根节点:

TreeNode* root = new TreeNode(val); 

找到根在中序中的位置:

int mid = pos[val]; 

取当前根:

int val = preorder[preIndex++]; 

这种写法相当于在 buildTree 函数内部,临时定义了一个递归函数,而不需要单独写成类的成员函数。


4. 调用入口
return dfs(dfs, 0, n - 1); 
  • 一开始整棵树对应的 中序区间 就是 [0, n - 1]
  • dfs 自己作为第一个参数传进去,用来实现递归。

五、复杂度分析

  • 时间复杂度
    • 建哈希表:O(n)
    • 每个节点在递归中只被处理一次、查哈希表一次:O(1)
       总时间复杂度:O(n)
  • 空间复杂度
    • 哈希表 pos 使用 O(n) 空间
    • 递归栈最坏情况(退化成链表)深度为 O(n)
       总空间复杂度:O(n)

六、更传统一点的方式(主要我想多用lambda表达式)

如果不想用 lambda,也可以写成比较传统的写法,把 dfs 写成类的成员函数:

class Solution { public: unordered_map<int, int> pos; vector<int> preorder; int preIndex = 0; TreeNode* buildTree(vector<int>& preorder_, vector<int>& inorder) { preorder = preorder_; int n = preorder.size(); pos.reserve(n * 2); for (int i = 0; i < n; ++i) pos[inorder[i]] = i; return dfs(0, n - 1); } TreeNode* dfs(int l, int r) { if (l > r) return nullptr; int val = preorder[preIndex++]; int mid = pos[val]; TreeNode* root = new TreeNode(val); root->left = dfs(l, mid - 1); root->right = dfs(mid + 1, r); return root; } }; 

Read more

Stable-Diffusion-v1-5-archive性能压测报告:QPS/延迟/显存占用三维度实测

Stable-Diffusion-v1-5-archive性能压测报告:QPS/延迟/显存占用三维度实测 想了解一个AI模型到底“快不快”、“稳不稳”、“贵不贵”?光看功能介绍可不够。今天,我们就拿经典的Stable Diffusion v1.5 Archive模型开刀,进行一次全方位的性能“体检”。我们将从三个核心维度——每秒处理能力(QPS)、响应延迟和显存占用——来实测它的表现,看看这个老牌文生图模型在今天的技术环境下,究竟实力如何。 1. 压测目标与方法论 在开始之前,我们先明确这次压测要回答的几个关键问题: 1. 极限性能:在单张GPU上,这个模型最高能承受多大的并发请求压力? 2. 响应速度:从用户提交请求到拿到图片,平均需要等待多久? 3. 资源消耗:运行这个服务,到底需要吃掉多少显存?成本高不高? 4. 稳定性:在高负载下,服务会不会崩溃?生成质量会不会下降? 为了回答这些问题,我们设计了一套压测方案。测试环境基于一台配备了单张NVIDIA RTX

By Ne0inhk
【花雕学编程】Arduino BLDC 之自主巡逻机器人(避障+路径规划)

【花雕学编程】Arduino BLDC 之自主巡逻机器人(避障+路径规划)

基于 Arduino 的无刷直流电机(BLDC)自主巡逻机器人(避障+路径规划),是一个融合了高效动力系统、多传感器环境感知、嵌入式实时计算与智能决策算法的复杂移动机器人系统。它旨在替代人工在预设或未知环境中进行长时间、高效率的巡查任务,通过 BLDC 电机提供持久且敏捷的驱动力,并利用算法实现环境理解与自主导航。 1、主要特点 高效长续航 BLDC 驱动系统 BLDC 电机是巡逻机器人的“心脏”,决定了其机动性与作业时长。 高效率与长续航: 相较于有刷电机,BLDC 电机效率通常高于 85%,发热量低。配合电子调速器(ESC)的 FOC(磁场定向控制)算法,能最大限度地利用电池能量,确保机器人能够持续工作 8 小时甚至更长时间,满足长时间巡逻的需求。 高动态响应: 巡逻过程中常需急停、避让行人或车辆。BLDC 电机具备快速启停和快速加减速的能力,配合差速转向底盘,能迅速响应避障算法发出的紧急制动或转向指令,保证运行安全。

By Ne0inhk
【硬核实战】Mac mini M4 部署 OpenClaw + Ollama 本地大模型:从零到一打通飞书机器人

【硬核实战】Mac mini M4 部署 OpenClaw + Ollama 本地大模型:从零到一打通飞书机器人

文章目录 * 一、 核心环境准备 * 二、 避坑指南:环境初始化在 Mac 终端部署时,首要解决的是权限与路径问题。 * 1. 终端常用快捷键* `Control + C`:强制停止当前运行的命令(如安装卡死时)。 * 2. Node.js 环境修复若遇到 `zsh: command not found: openclaw`,说明 NVM 路径未加载。 * 3. 临时加载环境 * 4. 永久写入配置 * 三、 模型选择:M4 性能调优 * 四、 OpenClaw 配置手术 (JSON 详解) * 五、 飞书机器人接入:最后的临门一脚 * 六、 运行与调试 * 启动 Gateway * 第一次发消息需授权 (Pairing) * 💡 结语

By Ne0inhk
低空经济新实践:无人机如何革新光伏电站巡检

低空经济新实践:无人机如何革新光伏电站巡检

引言:当低空经济遇见新能源革命 在“双碳”战略引领下,光伏电站如雨后春笋般遍布神州大地。截至2023年底,我国光伏发电装机容量已突破6亿千瓦,连续多年位居全球首位。然而,随着光伏电站规模的急剧扩大,传统人工巡检方式已难以满足高效、精准的运维需求。此时,低空经济的崛起为这一痛点带来了创新解法——无人机光伏巡检技术正在重新定义新能源设施的运维模式。 一、传统光伏巡检之困:低效、高风险、不精准 传统光伏巡检主要依赖人工方式,运维人员需要手持红外热像仪等设备,在光伏板阵列中徒步检查。这种方式存在明显短板: 1. 效率低下:一个100MW的光伏电站,人工全面巡检往往需要数周时间 2. 安全风险:高温、高电压环境下作业,人员安全隐患不容忽视 3. 漏检率高:人工目视检查难以发现细微缺陷,问题检出率通常不足70% 4. 数据离散:检查结果依赖个人经验,难以形成标准化数据资产 二、无人机智能巡检系统架构 现代无人机光伏巡检已形成完整的系统解决方案,主要由以下核心模块组成: 2.1 硬件配置 * 飞行平台:

By Ne0inhk