← 返回题目列表

如何根据前序遍历序列构造二叉搜索树?

高频 中等 第 7 / 27 题 更新于 2026/08/03
BST前序遍历构造树上下界

简化版

BST 前序序列的第一个值是根,后面先是一段小于根的左子树,再是一段大于根的右子树。更高效的做法是用全局下标和上下界递归构造,每个值只消费一次,时间 O(n)。

详细版

朴素做法是按前序顺序逐个插入 BST,平均 O(n log n),最坏退化 O(n²)。更好的构造方式是利用 BST 的值域约束:

  1. 维护下标 i 指向下一个待使用的前序值;
  2. 递归函数接收允许范围 (low, high)
  3. preorder[i] 不在范围内,说明它不属于当前子树,返回 null;
  4. 否则用它创建节点并 i++
  5. 左子树范围变成 (low, root.val)
  6. 右子树范围变成 (root.val, high)

每个值只被创建一次,时间 O(n),递归栈空间 O(h)。如果允许重复值,需要明确重复值放左边还是右边,并调整边界。

完整版教学

一、前序序列里隐藏了哪些信息

前序遍历顺序是根、左、右,所以序列第一个元素一定是整棵树的根。对于 BST 来说,左子树所有值小于根,右子树所有值大于根。因此在根后面的序列里,会先出现一段属于左子树的值,再出现一段属于右子树的值。

例如 preorder = [8,5,1,7,10,12]。根是 8,后面小于 8 的 [5,1,7] 属于左子树,大于 8 的 [10,12] 属于右子树。这个划分递归下去,就能恢复整棵 BST。

记忆钩子:前序给「谁先当根」,BST 给「值该落在哪个区间」;两者合起来就能构造树。

二、为什么逐个插入不是最优

按序插入当然能得到一棵 BST,因为前序序列里的节点值都来自目标树。但插入一个节点需要从根一路比较到叶子,单次代价 O(h)。如果树平衡,h≈log n,总时间 O(n log n);如果序列是 [1,2,3,4,5],树会退化成链表,插入总时间 O(n²)。

插入 1: 比较 0 次
插入 2: 比较 1 次
插入 3: 比较 2 次
插入 4: 比较 3 次
总比较 ≈ 0+1+2+...+(n-1)=O(n²)

上下界递归避免了反复从根查位置。它让每个值在前序流中只被检查和消费一次。

三、上下界递归的核心思想

递归函数 build(low, high) 表示「现在要构造一棵所有值都落在 (low, high) 内的子树」。如果当前前序值不在这个区间,它就不属于这棵子树,不能消费,直接返回 null。若它在区间内,它就是当前子树的根。

对于根值 x

  • 左子树只能放 (low, x)
  • 右子树只能放 (x, high)

这样递归时不需要显式扫描哪里是左右子树分界点,边界会自动判断下一个值是否能进入当前子树。

四、代码怎么写

代码里最关键的是 idx 只向前走,不回退。当前值不属于某个子树时,返回 null,但不要 idx++,因为这个值可能属于某个祖先的右子树。

int idx = 0;

TreeNode bstFromPreorder(int[] preorder) {
    return build(preorder, Long.MIN_VALUE, Long.MAX_VALUE);
}

TreeNode build(int[] pre, long low, long high) {
    if (idx == pre.length) return null;
    int val = pre[idx];
    if (val <= low || val >= high) return null;

    idx++;
    TreeNode root = new TreeNode(val);
    root.left = build(pre, low, val);
    root.right = build(pre, val, high);
    return root;
}

long 边界是为了避开节点值等于 Integer.MIN_VALUEInteger.MAX_VALUE 时的哨兵冲突。严格 BST 用开区间;如果题目允许重复值,条件要按重复值规则改。

五、用例推演

[8,5,1,7,10,12] 为例:

当前区间当前值结果
(-∞,+∞)8创建根 8
(-∞,8)5创建 8 的左子树根 5
(-∞,5)1创建 5 的左子树根 1
(-∞,1)7不在范围,返回 null
(1,5)7不在范围,返回 null
(5,8)7创建 5 的右子树根 7

注意值 7 在构造 1 的左右子树时都没有被消费,直到回到适合它的 (5,8) 区间才创建节点。这就是全局下标配上下界的精妙之处。

六、和「前序 + 中序」构造有什么区别

普通二叉树仅凭前序无法唯一确定,因为不知道左右子树边界。但 BST 的有序约束补上了缺失的信息,所以只给前序也能构造。前序负责提供根的出现顺序,BST 负责提供每个根的合法值域。

如果题目给的是普通二叉树,要用前序 + 中序或后序 + 中序来唯一构造;如果题目明确是 BST,前序一个序列就够了。这个区别经常被面试官拿来追问。

七、常见误区与追问

  • 误区:前序序列单独不能构造任何树。 对普通二叉树确实不够,但对 BST 足够,因为有值域约束。
  • 追问:为什么当前值不在区间时不能消费它? 它可能属于祖先的右子树,消费掉会破坏后续构造。
  • 误区:逐个插入就是最优解。 它最坏 O(n²),上下界递归可以 O(n)。
  • 追问:重复值怎么处理? 必须明确重复值放左还是右,并把开闭边界规则同步调整。
  • 误区:边界用 int 最小最大一定安全。 节点值可能等于边界,使用 long 哨兵更稳。

八、加强记忆

构造 BST 的关键是把「前序根优先」和「BST 值域」拼起来。递归函数只问一个问题:当前前序值能不能落进我的区间?能,就消费它并缩小左右子树边界;不能,就把它留给别的子树。全局下标只前进不回退,每个节点创建一次,所以时间 O(n)。记住:不在区间时返回 null,但绝对不要移动下标。