二叉树的后序遍历 图解题解
这道题到底在问什么
- 输入
- root=[1,null,2,3]
- 输出
- [3,2,1]
- 输入
- root=[1,2,3,4,5]
- 输出
- [4,5,2,3,1]
最优解:一步一步想明白
- 3记住「先左、再右、最后才记根」,记根的语句放在两个递归调用之后。下面逐帧走一遍这棵树。
- 4整棵树先看清这棵树:根是 50,它的左孩子 30(下面挂着 20、40)、右孩子 80(下面挂着 70)。后序遍历也从根 50 出发,但它要「左右子树都处理完才记根」,所以根 50 会是最后一个被记下的。
- 5进入 值50递归进入节点 值50(紫色)。后序的规矩是「根在最后」,所以一进来先不记 值50,先去把它的左、右子树处理完。
- 6进入 值30递归进入节点 值30(紫色)。后序的规矩是「根在最后」,所以一进来先不记 值30,先去把它的左、右子树处理完。
- 7进入 值20递归进入节点 值20(紫色)。后序的规矩是「根在最后」,所以一进来先不记 值20,先去把它的左、右子树处理完。
- 8记下 值20值20 是叶子节点,没有左右孩子,左右子树都为空,这时把它记进结果(它变绿),现在结果是 [20]。
- 9值20 子树完值20 这棵子树全部遍历完毕,从递归里返回到上一层 值30。
- 10回到 值30值30 的左子树整段都走完了,递归回到 值30。后序里左边完了还不能记根,先去它的右子树。
- 11进入 值40递归进入节点 值40(紫色)。后序的规矩是「根在最后」,所以一进来先不记 值40,先去把它的左、右子树处理完。
- 12记下 值40值40 是叶子节点,没有左右孩子,左右子树都为空,这时把它记进结果(它变绿),现在结果是 [20, 40]。
- 13值40 子树完值40 这棵子树全部遍历完毕,从递归里返回到上一层 值30。
- 14回到 值30值30 的右子树也走完了,递归回到 值30。左、右子树都处理完了,现在才轮到记根。
- 15记下 值30值30 的左右子树都遍历完了,这时才把 值30 记进结果(它变绿),现在结果是 [20, 40, 30]。
- 16值30 子树完值30 这棵子树全部遍历完毕,从递归里返回到上一层 值50。
- 17回到 值50值50 的左子树整段都走完了,递归回到 值50。后序里左边完了还不能记根,先去它的右子树。
- 18进入 值80递归进入节点 值80(紫色)。后序的规矩是「根在最后」,所以一进来先不记 值80,先去把它的左、右子树处理完。
- 19进入 值70递归进入节点 值70(紫色)。后序的规矩是「根在最后」,所以一进来先不记 值70,先去把它的左、右子树处理完。
- 20记下 值70值70 是叶子节点,没有左右孩子,左右子树都为空,这时把它记进结果(它变绿),现在结果是 [20, 40, 30, 70]。
- 21值70 子树完值70 这棵子树全部遍历完毕,从递归里返回到上一层 值80。
- 22值80 右空值80 没有右孩子,右子树为空。左、右子树都处理完了,现在才轮到记根。
- 23记下 值80值80 的左右子树都遍历完了,这时才把 值80 记进结果(它变绿),现在结果是 [20, 40, 30, 70, 80]。
- 24值80 子树完值80 这棵子树全部遍历完毕,从递归里返回到上一层 值50。
- 25回到 值50值50 的右子树也走完了,递归回到 值50。左、右子树都处理完了,现在才轮到记根。
- 26记下 值50值50 的左右子树都遍历完了,这时才把 值50 记进结果(它变绿),现在结果是 [20, 40, 30, 70, 80, 50]。
- 27值50 子树完值50 是根,它的整棵树都遍历完了,递归回到最外层,后序遍历到此结束。
- 28遍历完成整棵树后序遍历完成,结果是 20、40、30、70、80、50。回头看:每到一个节点都「先左、再右、最后才记自己」,所以根 50 最后才出现;递归栈帮我们记住回溯路径,每个节点恰好访问一次,所以是 O(n)。
⚠️ 容易写错的地方
✗ 错:把顺序写成 根→左→右 或 左→根→右
✓ 对:后序必须 左 → 右 → 根,记根的语句放在两个递归调用之后
记录语句的位置决定前/中/后序,放到最前就成了前序、放中间成了中序
✗ 错:忘了空节点的递归出口
✓ 对:进入 dfs 先判 node 为空就 return
不判空会对空指针取 left/right,直接崩溃或越界
✗ 错:迭代写法硬凑左右根顺序
✓ 对:可先按「根→右→左」做类前序,再把结果整体反转得到左右根
后序 = 「根右左」序列的逆序;直接迭代左右根要靠双栈或访问标记,反转法最好写
完整代码(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 postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
ans = []
def dfs(node):
if node:
dfs(node.left); dfs(node.right); ans.append(node.val)
dfs(root)
return ansC++
#include <algorithm>
#include <climits>
#include <functional>
#include <queue>
#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 *left, TreeNode *right) : val(x), left(left), right(right) {}
};
class Solution { public: vector<int> postorderTraversal(TreeNode* root){ vector<int> ans; function<void(TreeNode*)> dfs=[&](TreeNode* n){ if(!n) return; dfs(n->left); dfs(n->right); ans.push_back(n->val); }; dfs(root); return ans; } };Java
import java.util.*;
class TreeNode {
int val;
TreeNode left;
TreeNode 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 { public List<Integer> postorderTraversal(TreeNode root){ ArrayList<Integer> ans=new ArrayList<>(); dfs(root,ans); return ans; } void dfs(TreeNode n,List<Integer> ans){ if(n==null)return; dfs(n.left,ans); dfs(n.right,ans); ans.add(n.val); } }复杂度
时间
O(n)
每个节点恰好被访问、被记录一次,n 是节点总数
空间
O(h)
递归调用栈的深度等于树高 h;平衡树约 O(log n),退化成链时最坏 O(n)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 二叉树的后序遍历 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
不用递归怎么做后序?+
最好写的是「根→右→左 再反转」法:用一个栈,先压根;每次弹出栈顶记录其值,再先压左孩子、后压右孩子(让右孩子先被弹出),得到「根、右、左」的序列;最后把这个序列整体反转,就是后序「左、右、根」。也可以用双栈或给节点加访问标记,但反转法代码最短。
后序遍历有什么典型用处?+
后序「左右都处理完才处理根」的特性,天然适合「需要先拿到子树结果再算自己」的场景:比如求树的高度、判断平衡二叉树、删除整棵树(先删孩子再删根)、以及表达式树求值,都依赖后序的处理顺序。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 二叉树的后序遍历 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。