跳到正文
第 105 题中等

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

Construct Binary Tree from Preorder and Inorder Traversal

给定同一棵二叉树的先序与中序序列,把树重新构造出来并返回根节点。

在力扣中练习

题目描述

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

 

示例 1:

输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]

示例 2:

输入: preorder = [-1], inorder = [-1]
输出: [-1]

 

提示:

  • 1 <= preorder.length <= 3000
  • inorder.length == preorder.length
  • -3000 <= preorder[i], inorder[i] <= 3000
  • preorder 和 inorder 均 无重复 元素
  • inorder 均出现在 preorder
  • preorder 保证 为二叉树的前序遍历序列
  • inorder 保证 为二叉树的中序遍历序列

解题思路

主解法

分治 · 哈希表定位根

先序定根,中序定左右子树的边界

先序首项能确定根,但只知道根还无法分出左右子树。中序把“左子树、根、右子树”排在一起,因此根的中序位置能给出左子树的准确长度。

自拟主例 preorder=[8,4,6,12,10,15],inorder=[4,6,8,10,12,15],得到 [8,4,12,null,6,10,15]。根 8 左侧有两个节点,因此先序紧随其后的 4、6 属于左子树。

分步图解

先建立值到中序下标的映射。图中的浅色空位还没有节点。

构造进度 · 浅色占位尚未创建左子树 · 右子树
······
preorder · [0,6)
0
1
2
3
4
5
8
4
6
12
10
15
inorder · [0,6)
0
1
2
3
4
5
4
6
8
10
12
15
1 / 3
先序给根,中序给左子树长度;两个区间必须一起切。

递归始终维护两段左闭右开区间。设根位置为 k,左子树长度就是 k-inL;先序的左右起点也必须按这个长度同步移动,不能仅凭数值大小划分。

分步图解

先序首项 4 为根,中序位置 0,左子树 0 个节点。

leftSize
0
构造进度 · 浅色占位尚未创建左子树 · 右子树
84····
preorder · [1,3)
0
1
2
3
4
5
8
4
6
12
10
15
根
inorder · [0,2)
0
1
2
3
4
5
4
6
8
10
12
15
根
1 / 3
4 的左区间为空,因此 6 属于右孩子,不能画在正下方。

4 的左区间为空,所以 6 是它的右孩子。空区间直接返回空指针,其余位置分别递归创建并接回根。哈希表预存中序位置,避免每层重新线性查找。

时间 O(n)空间 O(n)

易错点

  • leftSize 写成 k - inL + 1,把根也算进左子树,左子树会多一个节点、右子树少一个。
  • 先序区间忘记跳过根,左子树递归时又把根建了一遍。
  • 空区间判断和区间开闭混用:这里统一左闭右开,终止条件是 preL >= preR,不是 preL > preR。
对照解法

分治 · 线性找根

不开哈希表,每次在 inorder 里扫出根的位置

结果正确,但每层线性查根最坏需要 O(n²);主解法用哈希表避免重复查找。

连哈希表都不要,直接在 inorder 里从 inL 往后扫,扫到等于 rootVal 的位置就是 k。

其余逻辑一字不差:切区间、递归、挂孩子。区别只在于每找一次根都要扫一段中序。

最坏情况是树退化成一条向左的链:每次找根都要把整段中序扫完,第 i 层扫 O(n - i) 个元素,总和 O(n²)。树比较平衡时,每一层的扫描长度按子问题规模折半,总代价接近 O(n log n)。

所以这不是「错」的写法,而是把 O(1) 的查表换回了线性扫描。面试时先写它、再补一句「加个哈希表就能做到 O(n)」,是很自然的加分路径。

  1. 1preIdx 指向当前该用的先序元素,初始为 0。
  2. 2build(inL, inR):区间为空返回 nullptr。
  3. 3取出 pre[preIdx++] 作为根,在 inorder 中从 inL 线性找到它的位置 k。
  4. 4递归建左右子树,preIdx 全程只向前走。
时间 O(n²) 最坏,O(n log n) 平均空间 O(n)

易错点

  • 把 preIdx 按值传递,递归返回后游标不前进,整段先序会被反复使用。
  • 先建右子树再建左子树:先序的顺序是「根 → 左 → 右」,顺序反了 preIdx 就对不上。

代码与演示

主解法配有图解;对照解法用于比较思路、复杂度或实现方式。

演示输入preorder = [8,4,6,12,10,15], inorder = [4,6,8,10,12,15]

先建立值到中序下标的映射。图中的浅色空位还没有节点。

构造进度 · 浅色占位尚未创建左子树 · 右子树
······
preorder · [0,6)
0
1
2
3
4
5
8
4
6
12
10
15
inorder · [0,6)
0
1
2
3
4
5
4
6
8
10
12
15
1 / 8
查看 C++ 代码

延伸阅读

  • 先序 + 中序、后序 + 中序都能唯一确定一棵树(106 是后序版,根在区间末尾)。
  • 先序 + 后序不行:某个节点只有一个孩子时,无法判断这个孩子是左还是右。
  • 「值互不相同」是这套下标切分的前提;有重复值时 pos 存不下唯一位置,题目会变成另一道题。