Maximum Depth of Binary Tree 是 LeetCode Hot 100 里面一道非常经典的二叉树递归题。
不就是求一棵树有多高吗?
确实,从题意上看,这题并不复杂。 但它之所以经典,不是因为题目难,而是因为它几乎是学习二叉树递归时最基础、最标准的一道模板题。
你给出的这份 Java 代码,使用的是这道题最经典的一种写法:
递归
而且这段代码非常简洁,几乎把“树的递归定义”直接翻译成了程序:
-
空树的深度是
0 -
一棵树的最大深度 = 左子树最大深度 和 右子树最大深度 中的较大值,再加上当前根节点这一层
本文就结合这段代码,系统地讲一下这道题的思路。
题目介绍
给定一个二叉树的根节点 root,要求你返回这棵树的 最大深度。
所谓最大深度,指的是:
从根节点到最远叶子节点的最长路径上的节点数。
注意这里说的是“节点数”,不是边数。
例如下面这棵树:
3
/ \
9 20
/ \
15 7
它的最大深度是:
3
因为从根节点 3 到最远叶子节点 15 或 7 的路径上,一共有 3 个节点:
3 -> 20 -> 15
或者:
3 -> 20 -> 7
示例
示例 1
输入:root = [3,9,20,null,null,15,7]
输出:3
因为树的最长路径从根到最深叶子节点一共经过 3 个节点。
示例 2
输入:root = [1,null,2]
输出:2
因为从根节点 1 到叶子节点 2 一共经过 2 个节点。
什么叫“最大深度”?
这题最容易混淆的一点,是“深度”和“高度”的口语理解。 不过在算法题里,通常我们可以这样记:
-
一棵空树深度为
0 -
一个只有根节点的树,深度为
1 -
每往下一层,深度就加一
所以对于一棵树来说:
最大深度,就是看它最深能走到第几层。
例如:
1
/
2
/
3
这棵树的最大深度就是:
3
因为最深的节点 3 在第 3 层。
为什么这道题特别适合递归?
因为二叉树本身就是递归结构。
对于一棵树来说:
-
它的左子树本身也是一棵树
-
它的右子树本身也是一棵树
而要求“整棵树的最大深度”,其实就可以拆成:
-
求左子树的最大深度
-
求右子树的最大深度
-
取其中较大的那个
-
再加上当前根节点这一层
你会发现,这种定义天然就非常适合递归。
这也是为什么这题经常被当作二叉树递归入门模板题。
核心思路
我们先想一个最关键的问题:
如果已经知道左子树和右子树的最大深度,那么当前树的最大深度怎么求?
答案非常直接:
maxDepth(root) = max(左子树深度, 右子树深度) + 1
为什么要 +1?
因为左子树和右子树的深度,都是从它们各自根节点开始算的。 而当前整棵树还要把当前这个根节点自己也算进去,所以要再加一层。
另外,递归一定要有终止条件。 这里最自然的终止条件就是:
如果当前节点为空,深度就是 0
于是整道题的递归逻辑就完整了。
Java 代码
下面是你给出的代码,我这里保留原始逻辑,并加上注释:
class Solution {
public int maxDepth(TreeNode root) {
// 空树深度为 0
if (root == null) return 0;
// 当前树的最大深度 = 左右子树最大深度中的较大值 + 1
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
}
代码拆解
这段代码非常短,但它几乎就是“树的递归定义”本身。 下面一步一步来看。
1. 为什么空节点返回 0?
if (root == null) return 0;
因为空树没有任何节点,所以它的深度自然是 0。
这一步既符合题意,也构成了递归终止条件。 如果没有这句,递归就无法停下来。
而且这个定义也非常自然:
-
空树深度为 0
-
只有一个根节点的树,深度就是 1
-
再往上就能一层层推出来
所以这是这题最基础、最关键的边界条件。
2. 为什么结果是 Math.max(maxDepth(root.left), maxDepth(root.right)) + 1?
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
因为对于当前节点 root 来说:
-
左子树的最大深度是
maxDepth(root.left) -
右子树的最大深度是
maxDepth(root.right)
而整棵树的最大深度,只取决于较深的那一边。
所以要先取:
Math.max(...)
然后再加上当前根节点这一层:
+ 1
这就是这道题最核心的状态转移。
手动模拟一遍
我们用经典例子来走一遍:
3
/ \
9 20
/ \
15 7
调用:
maxDepth(3)
处理节点 3
它会先去求:
-
maxDepth(9) -
maxDepth(20)
然后取较大值,再加 1。
处理节点 9
节点 9 没有左右孩子,所以:
-
maxDepth(null) = 0 -
maxDepth(null) = 0
于是:
maxDepth(9) = max(0,0) + 1 = 1
处理节点 20
节点 20 需要继续求:
-
maxDepth(15) -
maxDepth(7)
处理节点 15
15 没有左右孩子,所以:
maxDepth(15) = 1
处理节点 7
同理:
maxDepth(7) = 1
于是:
maxDepth(20) = max(1,1) + 1 = 2
回到节点 3
现在已经知道:
-
左子树深度 = 1
-
右子树深度 = 2
所以:
maxDepth(3) = max(1,2) + 1 = 3
最终答案就是:
3
为什么这个方法是正确的?
因为一棵树的最大深度,本质上就是:
从根节点出发,左右两边谁更深,就沿着谁往下走,并把当前节点这一层也算进去。
而递归恰好完整地模拟了这个过程:
-
先递归算出左子树深度
-
再递归算出右子树深度
-
取较大值
-
加上当前节点
此外:
-
空树深度定义为 0
-
单节点树深度自然是 1
所以这个递归定义和题目要求完全一致,因此一定正确。
复杂度分析
假设二叉树一共有 n 个节点。
时间复杂度
每个节点都会被访问一次,所以时间复杂度是:
O(n)
空间复杂度
额外空间主要来自递归调用栈。
最坏情况下,如果树退化成链表,递归深度会达到 n,所以空间复杂度是:
O(n)
如果树比较平衡,那么递归深度大约是树高:
O(log n)
但通常最坏情况写作 O(n)。
这道题真正想考什么?
这道题表面上不难,但它特别适合考察“你是否真正理解树递归”。
第一,是否能把树问题拆成左右子树问题
这是树递归最核心的能力。 只要你能想到:
当前答案 = 左子树答案 和 右子树答案 的某种组合
很多树题就会变得很自然。
第二,是否会写正确的递归终止条件
像这题里:
if (root == null) return 0;
这种边界看起来简单,但它是整段递归成立的基础。
第三,是否理解“当前层”为什么要加 1
很多初学者会把这一步忘掉。 但实际上,左右子树的深度只描述下面的层数,当前节点自己这一层也必须算进去。
这题还有别的写法吗?
有。
除了递归写法之外,这题还可以用:
层序遍历(BFS)
来做。
思路是:
-
用队列一层一层遍历树
-
每遍历完一层,深度加一
-
最后得到总层数
这种写法也很经典。
不过从简洁程度和递归结构的自然性来说,你现在这份递归代码是最适合作为主解法的。
和“二叉树的层序遍历”有什么关系?
前一题你写的是层序遍历,那题用的是 BFS。 而这题用的是递归 DFS。
它们虽然都能解决树问题,但角度不同:
层序遍历
关注的是:
-
每一层有哪些节点
最大深度
关注的是:
-
这棵树总共有多少层
-
哪条路径最深
所以这题更适合用 DFS 从下往上返回答案,而不是单纯地按层收集节点。
和“二叉树的最小深度”有什么区别?
这两题特别容易一起出现。
最大深度
max(左深度, 右深度) + 1
最小深度
不能简单写成:
min(左深度, 右深度) + 1
因为如果某一边为空,另一边不空,这样会出错。
所以“最大深度”这题虽然简单,但它也在帮你建立一个很重要的树递归基础,为后面的“最小深度”等题做准备。
总结
这道题的核心思想可以概括成一句话:
一棵二叉树的最大深度,等于它左右子树最大深度中的较大值,再加上当前根节点这一层。
具体做法就是:
-
如果当前节点为空,返回
0 -
递归求左子树最大深度
-
递归求右子树最大深度
-
返回两者较大值加一
代码非常简洁:
class Solution {
public int maxDepth(TreeNode root) {
if (root == null) return 0;
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
}
这是一道非常经典、也非常值得认真理解的二叉树基础题。 因为它会让你真正体会到:
-
树递归为什么天然适合“左右子树拆分”
-
边界条件在递归里有多重要
-
很多看似复杂的树问题,本质上都能写成很短的一行递推
把这道题真正吃透之后,再去看:
-
二叉树的最小深度
-
平衡二叉树
-
直径
-
路径总和
这些树递归题时,你会明显更容易抓住它们的共同结构。










