前端进阶之旅前端进阶之旅
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
首页程序员面试题库重建二叉树 前序中序 还原
算法算法树与遍历

如何根据前序和中序遍历结果重建一棵二叉树?

前序结果的首个节点必定是根,中序中根的位置把左右子树序列划分开,递归对切片重建即可。

前端进阶之旅 · 一题精讲更新于 2026.09.05
算法#树与遍历
先看核心答案读代码示例
理解线索

分治定位根并划分子树

  1. 根定位前序首值在中序中的下标决定左右长度
  2. 序列切割按左子树长度把前序剩余切成两份
  3. 递归终止序列为空时返回 null,叶子节点自然结束

节点值必须唯一,否则中序无法唯一确定根位置。

核心回答

先记住这个答案

前序遍历的第一个元素是树的根。在中序遍历中找到这个根,它左侧是左子树的中序序列,右侧是右子树的中序序列。根据左子树长度,把前序数组除根之外的部分砍成左前序和右前序。对左右子树分别递归执行相同操作,空序列返回 null。最终得到完整二叉树。核心是利用中序的左右分割和长度,反向推导出前序中左右子树的边界。

  • 前序首元素必为根节点
  • 中序中根下标分割左右子树
  • 递归切片重建,空序列返回空

前序定根、中序分界的递归机制

前序序列总是“根、左、右”的结构,所以第一个元素就是当前子树的根。去中序序列中寻找这个根的值,由于中序是“左、根、右”,根的下标把中序数组严密切分为左中序和右中序。左中序的长度正好等于左子树节点的个数,这一长度又用于从前序余下元素中分出左前序和右前序。

拿到左前序、左中序和右前序、右中序后,分别对它们调用同一个递归过程。递归的出口是传入的前序数组为空,此时显然没有节点,返回 null。每一层递归只负责创建一个根节点,并让左右指针指向子递归的返回值。整体过程类似对四个子数组做分治。

重建二叉树并验证三种输入JavaScript
function buildTree(pre, ino) {
  if (!pre.length) return null;
  const i = ino.indexOf(pre[0]);
  return {
    val: pre[0],
    left: buildTree(pre.slice(1, i + 1), ino.slice(0, i)),
    right: buildTree(pre.slice(i + 1), ino.slice(i + 1))
  };
}
function collect(t, p, i) {
  if (t) {
    p.push(t.val);
    collect(t.left, p, i);
    i.push(t.val);
    collect(t.right, p, i);
  }
}
function test(pre, ino) {
  const t = buildTree(pre, ino);
  const p = [], i = [];
  collect(t, p, i);
  console.log(JSON.stringify([p, i]));
}
test([1,2,4,7,3,5,6,8], [4,7,2,1,5,3,8,6]);
test([], []);
test([1], [1]);
查看输出与解释
[[1,2,4,7,3,5,6,8],[4,7,2,1,5,3,8,6]]
[[],[]]
[[1],[1]]

代码用切片递归重建,collect 同时收集前序和中序验证结果。第一个用例是普通多节点树,第二个为空树,第三个为单节点。每次 indexOf 扫描中序,最坏为 O(n²);每次 slice 也会复制 O(n) 长度的新数组,因此整体最坏时间仍为 O(n²)。建立哈希表可将时间优化到 O(n)。空间复杂度:递归栈深度最坏 O(n),但每层 slice 生成的临时数组累计可达 O(n²),故最坏空间为 O(n²);改用索引和哈希表可避免复制,将额外空间降至 O(n)。

前端从两条遍历数组恢复对象树

假设后端返回两个 JSON 数组,分别是一棵用户树的先序和中序遍历,节点值不会重复。前端拿到后需要恢复 Tree 对象供页面渲染。输入 pre=[1,2,4,7,3,5,6,8],ino=[4,7,2,1,5,3,8,6]。直接调用上述 buildTree,根部在中序的下标为 3(0 起始),左中序 [4,7,2] 左前序 [2,4,7],右子树同理。

节点数只有 8,每次 indexOf 线性扫描也很快。如果同一个接口迁到了千万级用户,但树深度可能达到数万,线性 indexOf 会让总时间升到 O(n²)。此时应预先生成“值 → 下标”哈希表,每轮找根位置变成 O(1),整体收敛到 O(n)。工程上还需在此之前校验两数组长度相等且都非 null。

输入非法或重复值的灾难边界

若节点值在中序中出现多次,例如前序 [1,1] 和中序 [1,1],indexOf 总是选中第一个 1,把另一个错误地划到左或右,最终结果不稳定且不符合“每棵树唯一重建”的前提。这类题目通常约定所有节点值互不相同,否则需要额外规则(比如固定取最左位置)但仍然得不到唯一树。

另一类常见失败是数组本身不等长或属于两棵不同树。此时可能在 indexOf 返回 -1,导致切片出现负长度或 undefined。务必要先验证长度相等并检查每个值在中序中确实出现。合法性扫描是 O(n),可放到构建函数之前做一次,避免递归深层遇到逻辑异常。

回答前,多想一步

容易答错的地方

误把前序+后序也能重建
只看前序和后序时,根和左右孩子的边界会被后序颠倒,无法唯一区分左右。本题之所以可行是因为中序恰好提供了左右子树边界。前序+后序不能唯一确定,那是另一个问题的结论。
把根下标当作数组位置直接切片
有些实现用 ino.indexOf(pre[0]) 返回的下标直接切前序,忘了左右子树的节点数正好等于该下标值。正确做法是左子树长度等于下标值,用它去决定前序 slice 的终点。
试着用自己的话回答

面试官还会怎么问?

如果中序存在重复值,但前序每个值也重复,怎么保证唯一?

无法保证。没有额外规则决定重复根应关联中序的哪个位置,常见技巧是禁止重复值或给定中序中相同值的方向约定,但那是人为约束,不属于原始问题。

每次 slice 复制数组的空间开销能避免吗?

可以。把左右边界下标(lo/hi)作为参数传递,配合哈希表使位置 O(1) 找到,构建时不再产生新数组。递归过程只增加调用深度,额外空间变为 O(n)(哈希表)加 O(h)(递归栈),总空间为 O(n+h),在 n≥h 时即 O(n)。

空数组和 null 输入如何区分?

空数组表示空树,应返回 null;null 则通常视为调用错误,应在入口处抛出异常。递归基只检查 pre.length === 0,无需为 null 写特判,但函数开头可加防御。

从一道题,走向一组知识

把知识连起来

树与遍历

如何求一棵二叉树的最大深度?

同属「树与遍历」专题,接着看 二叉树 最大深度 高度 递归计算 在具体场景中的处理方式。

搜索与排序

二分查找如何找到目标值的第一个出现位置?

继续了解 二分查找 第一个出现位置 首次出现,补充本题涉及的 算法 相关知识。

参考资料

  • Algorithms for Competitive Programming¶

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 前序定根、中序分界的递归机制
  3. 前端从两条遍历数组恢复对象树
  4. 输入非法或重复值的灾难边界
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑