LC145 · 后序遍历 · 工程执行实验室
左右完才记录根
后序遍历 = 左右子树都处理完之后,才把当前节点加入结果。用 current、ans、 递归栈/显式栈把执行过程讲清楚,并分开理解递归版与迭代逆序法。
3
/ \
9 20
/ \
15 7核心心智模型
后序遍历 = 左右子树都处理完之后,才把当前节点加入结果。
- · 访问顺序口诀:左子树 → 右子树 → 根(左-右-根)。
- · 与前序/中序最大区别:根节点不能一进入就输出,必须等左右都走完。
- · 递归返回后自然保证顺序:dfs(Left) 和 dfs(Right) 都 return 了,才 append。
ans 中保存的是左右子树均已处理完的节点;call stack 中保存的是已进入但尚未 append 的节点。
先 dfs(Left)、再 dfs(Right),左右都 return 后才 append。暂停状态由系统调用栈保存。
用栈做根-右-左 遍历收集到 rev,最后 reverse 得到后序。不能简单对称前序压栈顺序。
题目与输入:示例树与后序目标输出
给定二叉树:根 3,左孩子 9,右子树 20-15-7。后序遍历的目标输出是 [9, 15, 7, 20, 3]——整棵左子树、整棵右子树都处理完后,根 3 才最后出现。
先看清树结构和期望序列,后面每一步都能对照「为什么根在最后」。
状态表 · current / ans / 栈
| current | ans | stack(显式,栈顶在右) | 下一步 | 动作 |
|---|---|---|---|---|
| current = 3(节点 1) | [] | [] | 即将学习后序规则 | 读题 |
目标输出 [9,15,7,20,3]
1func postorderTraversal(root *TreeNode) []int {2 res := []int{}34 var dfs func(node *TreeNode)5 dfs = func(node *TreeNode) {6 if node == nil {7 return8 }910 dfs(node.Left)11 dfs(node.Right)12 res = append(res, node.Val)13 }1415 dfs(root)16 return res17}
展开迭代版(对比 reverse 技巧)
func postorderTraversal(root *TreeNode) []int {
if root == nil {
return nil
}
rev := []int{}
stack := []*TreeNode{root}
for len(stack) > 0 {
n := len(stack)
node := stack[n-1]
stack = stack[:n-1]
rev = append(rev, node.Val)
if node.Left != nil {
stack = append(stack, node.Left)
}
if node.Right != nil {
stack = append(stack, node.Right)
}
}
// reverse rev → 后序
for i, j := 0, len(rev)-1; i < j; i, j = i+1, j-1 {
rev[i], rev[j] = rev[j], rev[i]
}
return rev
}- 时间
- O(n)
- 空间
- O(h)
每个节点访问一次,共 n 个节点。
h 为树高;递归用系统调用栈,迭代用显式 stack,最坏退化链表时 O(n)。
Morris 后序遍历可做到额外空间 O(1),但本页主讲递归/栈解法。
后序遍历按照 左-右-根 的顺序访问。递归写法是先 dfs(Left)、再 dfs(Right),最后 append 当前节点;与前序/中序的差别在于 append 时机——必须等左右子树都处理完。迭代可用「根-右-左」遍历再 reverse 得到后序。每个节点访问一次,时间 O(n),空间 O(h),最坏 O(n),不是 O(1)。
这是前序写法。后序必须等 dfs(Left) 和 dfs(Right) 都返回后,才 append 当前节点。
后序迭代不能简单「先压左再压右」。常用技巧是根-右-左遍历后 reverse,或双栈/lastVisited 标记。
递归/显式栈版本空间是 O(h),最坏 O(n),不是 O(1)。
空树应直接返回空切片,否则会对空指针解引用。