从前序和中序遍历重建二叉树 —— 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

手把手教你配置飞书 OpenClaw 机器人,打造企业级 AI 智能助手

手把手教你配置飞书 OpenClaw 机器人,打造企业级 AI 智能助手

目标:在飞书(Feishu/Lark)中添加 OpenClaw 机器人,实现 7×24 小时 AI 智能对话与自动化办公。 OpenClaw GitHub | feishu-openclaw 桥接项目 想让你的机器人具备语音交互能力?试试 Seeed Studio 的 ReSpeaker 系列吧! 我会后续出reSpeaker XVF3800与Openclaw联动实现语音输入的教程,完全开放源码。 reSpeaker XVF3800 是一款基于 XMOS XVF3800 芯片的专业级 4 麦克风圆形阵列麦克风,即使在嘈杂的环境中也能清晰地拾取目标语音。它具备双模式、360° 远场语音拾取(最远 5 米)、自动回声消除 (AEC)、自动增益控制 (AGC)、声源定位 (DoA)、去混响、波束成形和噪声抑制等功能。

By Ne0inhk

电商客服机器人实战:SGLang+DeepSeek快速落地

电商客服机器人实战:SGLang+DeepSeek快速落地 1. 为什么电商客服需要SGLang这样的推理框架? 你有没有遇到过这样的场景:大促期间,客服咨询量暴增3倍,人工坐席全在线仍排队200+,用户等5分钟没回复直接关页面?或者,刚上线的AI客服回答“订单状态”还行,但一问“能不能把这件T恤换成同款蓝色,差价我补”,就卡壳说“我正在学习中”? 这不是模型能力不行,而是传统部署方式拖了后腿。 很多团队用vLLM或Ollama跑DeepSeek,结果发现: * 多轮对话时,每轮都重算前面所有token,GPU显存吃紧,吞吐掉一半; * 想让模型返回标准JSON格式(比如{"action": "exchange", "sku": "DS-2024-BLUE", "refund": 12.5}),得靠后处理正则清洗,出错率高还慢; * 写个“先查订单→

By Ne0inhk

亲测Z-Image-ComfyUI:AI绘画中文提示词效果惊艳

亲测Z-Image-ComfyUI:AI绘画中文提示词效果惊艳 最近在本地部署了阿里新开源的 Z-Image-ComfyUI 镜像,连续测试了三天,从“试试看”到“真香”,再到“这中文理解也太准了吧”,整个过程像拆开一个层层惊喜的盲盒。最让我意外的不是它出图快、显存占用低,而是——输入一句大白话中文,它真的能听懂、记得住、画得准。 过去用 Stable Diffusion 系列模型时,中文提示词总像隔着一层毛玻璃:写“水墨风山水画”,结果冒出半张人脸;写“穿旗袍的女士坐在苏州园林亭子里”,人物站姿歪斜、亭子比例失真、连“苏州”两个字都可能被误读成“苏洲”。而 Z-Image-Turbo 在同一台 RTX 4090(16G 显存)上跑起来,不仅生成速度肉眼可见地快,更关键的是——它对中文语义的理解,是真正“语义级”的,

By Ne0inhk
Sublime配置verilog开发环境-具备语法高亮、代码补全、自定义代码段及语法检查等功能,提升FPGA开发效率!

Sublime配置verilog开发环境-具备语法高亮、代码补全、自定义代码段及语法检查等功能,提升FPGA开发效率!

对于在学习FPGA开发之前使用过其他集成开发工具如VS、pycharm、keil或编辑工具如Sublime、VScode、Notepad的朋友,在使用Vivado时可能会像博主一样感觉自带编辑器用起来不太舒服,比如不支持语法高亮显示,不支持代码自动补全等功能。因次,使用第三方编辑器来编写Verilog代码是很有必要的。 本文将详细介绍如何在文本编辑器Sublime中配置verilog开发环境,最终实现语法高亮、代码补全、自定义代码段及语法检查等功能,使得可以在Sublime中高效编写verilog代码,大幅提升FPGA开发效率!附带自己在配置中的踩坑经验,希望朋友们按着下面的流程走可以一步配置到位!下面两图为使用Vivado编写代码及使用Sublime编写代码的对比图。 1.Sublime的介绍与安装配置         Sublime Text,是一款由 Sublime HQ 开发的跨平台轻量级代码编辑器,以 “启动快、插件丰富、自定义性强” 为核心特点,广泛用于代码编写、文本编辑和开发效率提升,支持 Windows、macOS、Linux 三大操作系统。

By Ne0inhk