前序遍历构造二叉搜索树 图解题解
这道题到底在问什么
- 输入
- preorder = 10,5,3,7,15,12,18
- 输出
- BST 层序 10,5,15,3,7,12,18
- 输入
- preorder = 4,5
- 输出
- 4 的右孩子是 5
最优解:一步一步想明白
- 3记住「取首元素当根;下一个值 ≤ bound 就往当前子树挂、> bound 就回退给祖先;左子树上界收成根值、右子树上界沿用父界」,下面逐帧套它。
- 4上界 +∞轮到根,上界是 +∞。下一个待读值 10 满足 10 ≤ +∞,说明它属于这棵子树,就在这儿建节点 10。
- 5读到第 1 个建好节点 10(紫色),读取指针前进一格。接着递归两边:左孩子的上界收紧成 10(左子树都要比 10 小),右孩子的上界沿用父亲传来的 +∞。
- 6上界 10轮到节点 10 的左孩子,上界是 10。下一个待读值 5 满足 5 ≤ 10,说明它属于这棵子树,就在这儿建节点 5。
- 7读到第 2 个建好节点 5(紫色),读取指针前进一格。接着递归两边:左孩子的上界收紧成 5(左子树都要比 5 小),右孩子的上界沿用父亲传来的 10。
- 8上界 5轮到节点 5 的左孩子,上界是 5。下一个待读值 3 满足 3 ≤ 5,说明它属于这棵子树,就在这儿建节点 3。
- 9读到第 3 个建好节点 3(紫色),读取指针前进一格。接着递归两边:左孩子的上界收紧成 3(左子树都要比 3 小),右孩子的上界沿用父亲传来的 5。
- 10超过上界轮到节点 3 的左孩子,它的上界是 3。下一个待读值 7 已经 7 > 3,超过上界,说明它不属于这里,这个位置判为空、不取值,交回给上一层去接。
- 11超过上界轮到节点 3 的右孩子,它的上界是 5。下一个待读值 7 已经 7 > 5,超过上界,说明它不属于这里,这个位置判为空、不取值,交回给上一层去接。
- 12上界 10轮到节点 5 的右孩子,上界是 10。下一个待读值 7 满足 7 ≤ 10,说明它属于这棵子树,就在这儿建节点 7。
- 13读到第 4 个建好节点 7(紫色),读取指针前进一格。接着递归两边:左孩子的上界收紧成 7(左子树都要比 7 小),右孩子的上界沿用父亲传来的 10。
- 14超过上界轮到节点 7 的左孩子,它的上界是 7。下一个待读值 15 已经 15 > 7,超过上界,说明它不属于这里,这个位置判为空、不取值,交回给上一层去接。
- 15超过上界轮到节点 7 的右孩子,它的上界是 10。下一个待读值 15 已经 15 > 10,超过上界,说明它不属于这里,这个位置判为空、不取值,交回给上一层去接。
- 16上界 +∞轮到节点 10 的右孩子,上界是 +∞。下一个待读值 15 满足 15 ≤ +∞,说明它属于这棵子树,就在这儿建节点 15。
- 17读到第 5 个建好节点 15(紫色),读取指针前进一格。接着递归两边:左孩子的上界收紧成 15(左子树都要比 15 小),右孩子的上界沿用父亲传来的 +∞。
- 18上界 15轮到节点 15 的左孩子,上界是 15。下一个待读值 12 满足 12 ≤ 15,说明它属于这棵子树,就在这儿建节点 12。
- 19读到第 6 个建好节点 12(紫色),读取指针前进一格。接着递归两边:左孩子的上界收紧成 12(左子树都要比 12 小),右孩子的上界沿用父亲传来的 15。
- 20超过上界轮到节点 12 的左孩子,它的上界是 12。下一个待读值 18 已经 18 > 12,超过上界,说明它不属于这里,这个位置判为空、不取值,交回给上一层去接。
- 21超过上界轮到节点 12 的右孩子,它的上界是 15。下一个待读值 18 已经 18 > 15,超过上界,说明它不属于这里,这个位置判为空、不取值,交回给上一层去接。
- 22上界 +∞轮到节点 15 的右孩子,上界是 +∞。下一个待读值 18 满足 18 ≤ +∞,说明它属于这棵子树,就在这儿建节点 18。
- 23读到第 7 个建好节点 18(紫色),读取指针前进一格。接着递归两边:左孩子的上界收紧成 18(左子树都要比 18 小),右孩子的上界沿用父亲传来的 +∞。
- 24已读完轮到节点 18 的左孩子,但前序串已经全部读完、没有值可取了,这个位置判为空,回到上一层。
- 25已读完轮到节点 18 的右孩子,但前序串已经全部读完、没有值可取了,这个位置判为空,回到上一层。
- 26原样重建前序串读完,整棵 BST 重建好了:根 10,左 5(挂 3、7)、右 15(挂 12、18)。验证它的中序是 3,5,7,10,12,15,18,严格升序,确实是合法 BST。回头看,每个值只被取用一次、靠一个上界就分到了该去的位置,一遍 O(n) 搞定。
⚠️ 容易写错的地方
✗ 错:每层都重新扫数组找左右分界
✓ 对:用一个上界 bound + 只前进的指针 i
重新扫是 O(n²);单上界递归让每个值只看一次,才是 O(n)
✗ 错:超过上界时还把指针前进了
✓ 对:超界就返回空、i 不动
i 一旦多走,这个本该交给祖先的值就被丢了,树会建错
✗ 错:左右子树上界写反
✓ 对:左孩子收紧为根值、右孩子沿用父界
BST 左子树都小于根,所以上界收成根值;右子树只受祖先约束,沿用父亲的 bound
完整代码(Python / C++ / Java)
Python
from typing import List, Optional
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def bstFromPreorder(self, preorder: List[int]) -> Optional[TreeNode]:
i = 0
def build(bound):
nonlocal i
if i == len(preorder) or preorder[i] > bound: return None
root = TreeNode(preorder[i]); i += 1
root.left = build(root.val)
root.right = build(bound)
return root
return build(float('inf'))C++
#include <algorithm>
#include <climits>
#include <functional>
#include <iterator>
#include <numeric>
#include <vector>
using namespace std;
struct TreeNode{int val;TreeNode* left;TreeNode* right;TreeNode():val(0),left(nullptr),right(nullptr){} TreeNode(int x):val(x),left(nullptr),right(nullptr){} TreeNode(int x,TreeNode*l,TreeNode*r):val(x),left(l),right(r){}};
class Solution{ int i=0; public: TreeNode* bstFromPreorder(vector<int>& preorder){ i=0; return build(preorder,INT_MAX);} TreeNode* build(vector<int>& a,int bound){ if(i==(int)a.size()||a[i]>bound)return nullptr; TreeNode* r=new TreeNode(a[i++]); r->left=build(a,r->val); r->right=build(a,bound); return r;} };Java
import java.util.*;
class TreeNode{int val;TreeNode left,right;TreeNode(){} TreeNode(int val){this.val=val;} TreeNode(int val,TreeNode left,TreeNode right){this.val=val;this.left=left;this.right=right;}}
class Solution{ int i; public TreeNode bstFromPreorder(int[] preorder){ i=0; return build(preorder,Integer.MAX_VALUE);} TreeNode build(int[] a,int bound){ if(i==a.length||a[i]>bound)return null; TreeNode r=new TreeNode(a[i++]); r.left=build(a,r.val); r.right=build(a,bound); return r;} }复杂度
时间
O(n)
读取指针 i 只前进、不回退,每个值取用一次;每次上界比较是常数,共 O(n)
空间
O(h)
只用递归栈,深度等于树高 h;最坏退化成单边链时 O(n),平衡时 O(log n)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 前序遍历构造二叉搜索树 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
除了上界递归,这题还有别的解法吗?+
有。一种是单调栈:遍历前序值,维护一个递减栈,新值若小于栈顶就当栈顶的左孩子,否则不断弹栈、最后一个被弹出的节点的右孩子接上新值。另一种是「前序 + 排序得中序,再用前序加中序建树」,但要额外 O(n log n) 排序和哈希定位,不如单上界递归直接。三者都能做,单上界递归最简洁。
和已知前序、中序建普通二叉树(lc105)有什么区别?+
lc105 是普通二叉树,必须同时给前序和中序两个序列才能唯一确定结构。lc1008 是 BST,值有序这一点等价于「中序就是排好序的前序」,所以只给前序就够,用上界(或区间)就能把每个值定位,省掉中序输入。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 前序遍历构造二叉搜索树 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。