当前:LC106 · 从中序与后序遍历序列构造二叉树 · 首次出现于 Day 24 · 路径:顶栏「56天打卡」→ 点击 LC 题号 → 逐题动画

LC106 · Construct Binary Tree from Inorder and Postorder · 树 / 构建

中序 + 后序建树:后序末尾是根,先建右

后序最后访问根,所以从尾巴往回拿。拿完根,下一段属于右子树,必须先递归右区间,再递归左区间。先建左会把右子树的根错安到左边。

后序是左、右、根。当前子树的根是 postorder[postIndex],取完 postIndex 减一。中序里根把区间切成 [L,mid−1] 和 [mid+1,R]。因为从尾往前看,根的前一个区段是右子树,所以先 build 右再 build 左。时间 O(n),空间 O(n)。

时间 O(n)空间 O(n)结论先行 · 全文约 6 节
导读

给定一棵树的中序遍历和后序遍历,重建这棵二叉树。节点值互不相同。中序是左、根、右;后序是左、右、根。

主例 inorder = [9, 3, 15, 20, 7],postorder = [9, 15, 7, 20, 3]。后序最后一个数是 3,它就是整棵树的根。3 在中序里把数组切成左边 [9]、右边 [15, 20, 7]。重建结果层序是 [3, 9, 20, null, null, 15, 7]。

只知道后序能确定根,切不开左右;只知道中序能切开,找不到根。本文要回答:从尾巴往回消费后序时,为什么下一刀必须先建右子树。

从尾巴拿根,下一刀是右子树

后序最后访问根,所以整段 postorder 的最后一个元素就是当前这棵子树的根。根在中序里的位置 mid 一旦确定,左边那段只能是左子树,右边那段只能是右子树。这一步和前序+中序建树相同:中序负责划界。

差别出在消费方向。前序是根、左、右,从头往后拿,拿完根下一截是左子树,所以先建左。后序是左、右、根,从尾巴往前拿,拿完根下一截是右子树,所以先建右。缺的不是「根在哪」,而是「下一次 postIndex 指向的值属于哪一边」。把 LC105 的先左后右原样搬过来,主例会把 20 错安成 3 的左孩子。

用一个从尾巴往前走的下标 postIndex,每次 build 先取 postorder[postIndex] 做根,立刻减一。然后先递归中序右区间 [mid+1, R],再递归左区间 [L, mid−1]。区间空(L > R)返回空。中序下标用哈希表 O(1) 定位,避免每次扫描。

用手走主例。一开始 postIndex=4,L=0,R=4。根是 3,3 在中序下标 1。演示第一帧标的就是这一刀。右区间 [2,4] 对应 [15,20,7],先建它:postIndex 变成 3,根是 20,20 在中序下标 3。演示第二帧。20 的右区间 [4,4] 是 7,左区间 [2,2] 是 15,仍按先右后左拿走。回到 3 的左区间 [0,0],只剩 9。整棵是 3,左 9,右 20,20 下挂 15 和 7。

先建左会在 postIndex=3 时把 20 当成 3 的左孩子,中序左边却只有 9,立刻对不上。这不是实现细节,是后序「根之前是右子树」逼出来的调用顺序。节点值不重复,中序位置唯一;若有重复值,单靠这两个数组无法唯一确定树。

为什么必须先右从尾巴看后序:根、右子树、左子树。postIndex 减一之后指向的是右子树的根。先建左会把这个值错吃进左子树。
inorder=[9,3,15,20,7], postorder=[9,15,7,20,3]
中序9315207
后序9157203
根=3(后序末尾),mid=1

Go:postIndex 逆序

solution.goGo
func buildTree(inorder, postorder []int) *TreeNode {
pos := make(map[int]int, len(inorder))
for i, v := range inorder { pos[v] = i }
postIndex := len(postorder) - 1
var build func(int, int) *TreeNode
build = func(L, R int) *TreeNode {
if L > R { return nil }
rootVal := postorder[postIndex]
postIndex--
root := &TreeNode{Val: rootVal}
mid := pos[rootVal]
root.Right = build(mid+1, R)
root.Left = build(L, mid-1)
return root
}
return build(0, len(inorder)-1)
}

1中序值到下标,划界时 O(1) 找到 mid。

2postIndex 从尾巴起步。每次取根后立刻减一,下一个值留给右子树。

3空区间 L>R 返回空,必须写在取根之前。

4先递归右区间再递归左区间。这两行对调就会在主例上把 20 接到 3 的左边。

总结

后序末尾取根,中序切开;从尾巴看下一截是右子树,所以先建右。主例根 3,先建 20。

  • 后序最后访问根。主例第一个根是 3,下一个根是 20,不是 9。
  • 先建右是消费方向逼出来的。先建左会把右子树的根错吃进左边。
  • 中序哈希定位。空区间返回空。
同族题目
LC105从前序与中序遍历构造二叉树LC889根据前序和后序遍历构造二叉树LC145二叉树的后序遍历