二叉树的最大深度 图解题解
从根到最远叶子有多深?每个节点只需问两个孩子,然后取大加一。
二叉树最大深度就像问一个家族树最多传了几代:每个人只需问左右两个孩子各自下面还有多深,取较深的一支 +1(算上自己)往上报。一层层把答案汇总到根,根收到的就是全树深度。空节点报 0,是递归触底的起点,不会多算一层。
这道题到底在问什么
- 输入
- [3,9,20,4,7,15,8,2,1]
- 输出
- 4
最优解:为什么这么做
一句话答案:LeetCode 104 二叉树的最大深度用后序递归几行解决:空节点深度为 0,非空节点深度 = max(左子树深度, 右子树深度) + 1。必须先算完左右孩子才能算自己,所以是后序遍历的形状。每个节点访问一次,时间 O(n),递归栈空间 O(h)。
最大深度数的是节点还是边
题目定义的深度是「从根到最远叶子这条路径上的节点个数」:空树深度 0,只有一个根的树深度 1。这个口径要先钉死——如果按边数理解,单根树会算成 0,整个答案系统性地差 1。输入是一棵普通二叉树,输出一个整数。
为什么求树的深度自然想到递归
直接想「根到最远叶子有多远」不好下手,因为最远的叶子可能藏在任何一支里。换个问法就豁然开朗:整棵树的深度,跟它两棵子树的深度是什么关系?答案是——树的最深路径必然从根出发、走进左右子树之一,所以整棵树的深度 = 两棵子树深度的较大者再加上根自己这一层,即 max(left, right) + 1。
这一步把「算一棵大树」拆成了「算两棵小树」这两个一模一样的子问题,而子问题还能继续往下拆,直到空节点——空树没有任何节点,深度定为 0,这就是递归出口。整个解法就是把这条关系式原样写成代码。
为什么是后序遍历而不是前序
关系式 max(left, right) + 1 里,父节点的答案依赖两个孩子的答案:不先知道左右子树各有多深,就不知道该给谁加一。所以计算顺序必须是「先左子树、再右子树、最后自己」,这正是后序遍历的定义。落到执行上,递归会一路扎到最底层,叶子的深度最先确定(左右都是空,max(0,0)+1 = 1),然后一层层往上回传,每上一层取较大值加一,回到根时得到的就是整棵树的最大深度。
这个递归为什么一定算得对
正确性可以用归纳法一句话说清:空树深度 0 显然正确;假设左右子树的递归结果都对,那么根到最远叶子的路径必定经过较深的那棵子树,长度就是那棵子树的深度加上根这一个节点——恰好是 max(left, right) + 1。底层对、每一步组合也对,整体就对。这也是大多数二叉树递归题共同的论证套路:只要「出口正确 + 组合关系正确」,就不必担心中间过程。
复杂度与最容易写错的边界
时间 O(n):每个节点恰好被访问一次,每次只做一次比较和一次加法。空间 O(h):递归栈最深等于树高 h,平衡树约 O(log n),退化成链时 O(n)。
两个高频错误:一是递归出口把空节点深度写成 1——出口是整个递归的地基,写 1 会让所有答案多 1;二是把深度当成边数返回 max(left, right) 而忘了 +1,那个 +1 代表的是当前节点自己,漏掉答案就永远是 0。
▶ 动画逐步走查(共 21 步)——想跟着动画一帧帧对照就展开
- 3这正是后序遍历的形状:必须先把左右子树都算完,才能算自己。所以递归会一路扎到最底层的叶子,叶子的深度先定下来(都是 1),再一层层往上回传、取较大值 +1。下面逐节点看这个过程。
- 4后序 DFS 起点 = 根 3先看清这棵树:根是 3,往下到最远的叶子要走几层?接下来从根出发做后序 DFS——一路扎到底,叶子的深度先定,再一层层往上回传。
- 5visiting 3 → 先递归左右子树深度优先往下走,现在踩到节点 3。后序遍历的规矩是「左右子树先算完,自己才算」,所以先把它挂在递归栈上(绿),继续往左右孩子深入。
- 6visiting 9 → 先递归左右子树深度优先往下走,现在踩到节点 9。后序遍历的规矩是「左右子树先算完,自己才算」,所以先把它挂在递归栈上(绿),继续往左右孩子深入。
- 7visiting 4 → 先递归左右子树深度优先往下走,现在踩到节点 4。后序遍历的规矩是「左右子树先算完,自己才算」,所以先把它挂在递归栈上(绿),继续往左右孩子深入。
- 8visiting 2 → 先递归左右子树走到叶子节点 2。它没有孩子,左右子树深度都是 0,所以它自己的深度 = max(0,0)+1 = 1。后序遍历的妙处:最底层的答案最先确定。
- 92: max(0,0)+1 = 1节点 2 的左右子树都回来了:左空 0、右空 0。取较深的那个 +1,得到它的深度 = max(0,0)+1 = 1。算完就把它标蓝(已定),深度回传给父节点。
- 10visiting 1 → 先递归左右子树走到叶子节点 1。它没有孩子,左右子树深度都是 0,所以它自己的深度 = max(0,0)+1 = 1。后序遍历的妙处:最底层的答案最先确定。
- 111: max(0,0)+1 = 1节点 1 的左右子树都回来了:左空 0、右空 0。取较深的那个 +1,得到它的深度 = max(0,0)+1 = 1。算完就把它标蓝(已定),深度回传给父节点。
- 124: max(1,1)+1 = 2节点 4 的左右子树都回来了:左子树 1、右子树 1。取较深的那个 +1,得到它的深度 = max(1,1)+1 = 2。算完就把它标蓝(已定),深度回传给父节点。
- 13visiting 7 → 先递归左右子树走到叶子节点 7。它没有孩子,左右子树深度都是 0,所以它自己的深度 = max(0,0)+1 = 1。后序遍历的妙处:最底层的答案最先确定。
- 147: max(0,0)+1 = 1节点 7 的左右子树都回来了:左空 0、右空 0。取较深的那个 +1,得到它的深度 = max(0,0)+1 = 1。算完就把它标蓝(已定),深度回传给父节点。
- 159: max(2,1)+1 = 3节点 9 的左右子树都回来了:左子树 2、右子树 1。取较深的那个 +1,得到它的深度 = max(2,1)+1 = 3。算完就把它标蓝(已定),深度回传给父节点。
- 16visiting 20 → 先递归左右子树深度优先往下走,现在踩到节点 20。后序遍历的规矩是「左右子树先算完,自己才算」,所以先把它挂在递归栈上(绿),继续往左右孩子深入。
- 17visiting 15 → 先递归左右子树走到叶子节点 15。它没有孩子,左右子树深度都是 0,所以它自己的深度 = max(0,0)+1 = 1。后序遍历的妙处:最底层的答案最先确定。
- 1815: max(0,0)+1 = 1节点 15 的左右子树都回来了:左空 0、右空 0。取较深的那个 +1,得到它的深度 = max(0,0)+1 = 1。算完就把它标蓝(已定),深度回传给父节点。
- 19visiting 8 → 先递归左右子树走到叶子节点 8。它没有孩子,左右子树深度都是 0,所以它自己的深度 = max(0,0)+1 = 1。后序遍历的妙处:最底层的答案最先确定。
- 208: max(0,0)+1 = 1节点 8 的左右子树都回来了:左空 0、右空 0。取较深的那个 +1,得到它的深度 = max(0,0)+1 = 1。算完就把它标蓝(已定),深度回传给父节点。
- 2120: max(1,1)+1 = 2节点 20 的左右子树都回来了:左子树 1、右子树 1。取较深的那个 +1,得到它的深度 = max(1,1)+1 = 2。算完就把它标蓝(已定),深度回传给父节点。
- 223: max(3,2)+1 = 4节点 3 的左右子树都回来了:左子树 3、右子树 2。取较深的那个 +1,得到它的深度 = max(3,2)+1 = 4。算完就把它标蓝(已定),深度回传给父节点。
- 23root.depth = max(3,2)+1 = 4回到根节点 3:它左子树深度 3、右子树深度 2,取较深的 +1 = 4。这就是整棵树的最大深度——最长的一条路是 3→9→4→2 这样的 4 层。
⚠️ 容易写错的地方
✗ 错:空节点深度写成 1
✓ 对:空节点(null)深度是 0
这是递归出口,写错全盘皆错
✗ 错:把深度当成边数
✓ 对:本题数的是节点个数
单根树深度 = 1,不是 0
✗ 错:先算自己再算子树
✓ 对:后序:先左右子树,再回传自己
没算完子树就不知道该 +1 谁
完整代码(Java / Python / C++)
Java
class Solution {
public int maxDepth(TreeNode root) {
if (root == null) return 0; // 空节点深度 0(出口)
int left = maxDepth(root.left); // 后序:先算左
int right = maxDepth(root.right); // 再算右
return Math.max(left, right) + 1; // 较深子树 + 1
}
}Python
class Solution:
def maxDepth(self, root: TreeNode) -> int:
if root is None: # 空节点深度 0
return 0
left = self.maxDepth(root.left) # 后序:先算左
right = self.maxDepth(root.right) # 再算右
return max(left, right) + 1 # 较深子树 + 1C++
class Solution {
public:
int maxDepth(TreeNode* root) {
if (root == nullptr) return 0; // 空节点深度 0
int left = maxDepth(root->left); // 后序:先算左
int right = maxDepth(root->right); // 再算右
return max(left, right) + 1; // 较深子树 + 1
}
};复杂度
时间
O(n)
每个节点恰好访问一次
空间
O(h)
递归栈深 = 树高 h;最坏(链)O(n),平衡 O(log n)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 二叉树的最大深度 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
这道题为什么用「递归」,换最直接的暴力解会差在哪?+
递归抓住了本题的结构特征,把暴力解里重复的工作省掉;暴力解通常要多嵌套一层枚举,数据一大就超时。具体对比见上文「暴力解及其卡点」与「最优解逐步推演」两节。
时间复杂度为什么是 undefined?怎么推出来的?+
按上文复杂度小节的推导,时间复杂度为 undefined。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 二叉树的最大深度 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。