在数据结构与算法的世界中,二叉树是理解递归和层次遍历的绝佳载体。LeetCode二叉树的最大深度问题(第104题)看似简单,却蕴含着深刻的算法思想。其核心价值在于通过这个直观问题,系统性地训练递归思维的分解能力、掌握深度优先搜索(DFS)与广度优先搜索(BFS)两种核心遍历策略,并理解树的高度与深度的本质区别。掌握这道题,不仅是解决一个计算问题,更是打开了处理所有树形结构问题的大门。作为鳄鱼Java的资深内容编辑,我将为你深入剖析此题的多种解法,从递归的优雅到迭代的实用,助你建立坚实的树算法基础。
一、问题重述与核心概念:什么是树的深度?

题目要求:给定一个二叉树的根节点 `root`,返回其最大深度。二叉树的最大深度是指从根节点到最远叶子节点的最长路径上的节点数。
关键概念澄清:
在二叉树中,通常有两种定义:
1. **节点的深度**: 从根节点到该节点的边数。
2. **节点的高度**: 从该节点到最深叶子节点的边数。
树的最大深度等于根节点的高度。本题采用“节点数”定义,即空树深度为0,单节点树深度为1。这与从根到叶子节点的路径上经过的节点总数一致。
示例:
给定二叉树 `[3,9,20,null,null,15,7]`,其最大深度为3,因为最长路径 `3 -> 20 -> 15` 或 `3 -> 20 -> 7` 包含3个节点。
理解这个定义是正确解题的第一步。在鳄鱼Java社区的算法讨论中,清晰的概念区分是避免错误的前提。
二、递归解法(DFS):分而治之的优雅体现
递归是解决树问题最自然、最优雅的方法。其核心思想是将原问题分解为规模更小的相同子问题。
递归思路分解:
对于一棵以 `root` 为根的二叉树:
1. **基本情况**: 如果 `root` 为空(`null`),说明树不存在,深度为0。
2. **递归步骤**: 如果 `root` 不为空,那么整棵树的最大深度等于什么?
- 它等于**左子树的最大深度**与**右子树的最大深度**中的较大值。
- 然后,再加上当前根节点自身所占的一层深度(+1)。
递归公式:
`maxDepth(root) = 1 + max(maxDepth(root.left), maxDepth(root.right))`
Java代码实现:
```java /** * Definition for a binary tree node. * public 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 int maxDepth(TreeNode root) { // 递归终止条件:当前节点为空 if (root == null) { return 0; } // 递归计算左子树深度 int leftDepth = maxDepth(root.left); // 递归计算右子树深度 int rightDepth = maxDepth(root.right); // 当前树深度 = 左右子树深度的最大值 + 1(当前节点) return Math.max(leftDepth, rightDepth) + 1; } } ```
复杂度分析:
- **时间复杂度:O(n)**,其中 n 是树中的节点数。每个节点都会被递归函数访问一次且仅一次。
- **空间复杂度:O(h)**,其中 h 是树的高度。空间消耗来自递归调用栈,在最坏情况(树退化为链表)下为 O(n),在平衡树情况下为 O(log n)。
这种“自顶向下”或“后序遍历”式的递归,是理解LeetCode二叉树的最大深度问题最本质的方法,它深刻地体现了分治思想。
三、迭代解法 - BFS(层序遍历):模拟过程的直观展现
如果你觉得递归抽象,或者想避免递归的栈溢出风险(对于极深的树),迭代法是绝佳选择。广度优先搜索(BFS,即层序遍历)非常适合计算最大深度,因为深度本质上就是二叉树的层数。
BFS算法步骤:
1. 初始化一个队列(通常使用 `LinkedList`),并将根节点入队(如果非空)。
2. 初始化深度计数器 `depth = 0`。
3. 当队列不为空时,循环执行:
- 深度 `depth++`(意味着开始处理新的一层)。
- 获取当前队列的大小 `levelSize`,这个大小就是当前层的节点数。
- 通过一个内层循环,依次将当前层的 `levelSize` 个节点出队。
- 对于每个出队的节点,如果其有左孩子,则左孩子入队;如果其有右孩子,则右孩子入队。
4. 当队列为空时,表示所有层已遍历完毕,返回 `depth`。
Java代码实现:
```java
class Solution {
public int maxDepth(TreeNode root) {
if (root == null) return 0;
Queue
while (!queue.isEmpty()) {
int levelSize = queue.size(); // 当前层的节点数
depth++; // 进入新的一层,深度加1
// 遍历当前层的所有节点
for (int i = 0; i < levelSize; i++) {
TreeNode currentNode = queue.poll();
// 将下一层的节点加入队列
if (currentNode.left != null) queue.offer(currentNode.left);
if (currentNode.right != null) queue.offer(currentNode.right);
}
}
return depth;
}
}
<p><strong>复杂度分析</strong>:<br>
- **时间复杂度:O(n)**,每个节点入队、出队各一次。<br>
- **空间复杂度:O(w)**,其中 w 是树的最大宽度(即最宽一层的节点数)。在最坏情况下(完全二叉树底层),空间复杂度可能达到 O(n)。</p>
<p>BFS解法非常直观,它模拟了“一层一层往下探索”的过程,是解决<strong>LeetCode二叉树的最大深度</strong>的另一种高效且稳健的方式。</p>
<h2>四、迭代解法 - DFS(显式栈):递归的迭代翻译</h2>
<p>除了BFS,我们也可以用深度优先搜索(DFS)的迭代方式,手动使用栈来模拟递归调用的过程。这种方法通常需要栈中同时存储节点和该节点对应的当前深度。</p>
<p><strong>算法思路</strong>:<br>
使用一个栈,元素为 `Pair<TreeNode, Integer>`(节点,当前深度)。从根节点(深度1)开始,将其压栈。然后循环:弹出栈顶,用当前深度更新全局最大深度;如果该节点有右孩子,将右孩子及“当前深度+1”压栈;再压左孩子(先右后左是为了与某些遍历顺序一致,但顺序不影响最终深度计算)。</p>
<p><strong>Java代码实现(使用栈)</strong>:</p>
<p>```java
class Solution {
public int maxDepth(TreeNode root) {
if (root == null) return 0;
Deque<Pair<TreeNode, Integer>> stack = new ArrayDeque<>();
stack.push(new Pair<>(root, 1));
int maxDepth = 0;
while (!stack.isEmpty()) {
Pair<TreeNode, Integer> current = stack.pop();
TreeNode node = current.getKey();
int currentDepth = current.getValue();
maxDepth = Math.max(maxDepth, currentDepth); // 更新最大深度
// 先右后左,保证出栈顺序(但深度计算与顺序无关)
if (node.right != null) stack.push(new Pair<>(node.right, currentDepth + 1));
if (node.left != null) stack.push(new Pair<>(node.left, currentDepth + 1));
}
return maxDepth;
}
}
```</p>
<p><strong>复杂度分析</strong>:<br>
- **时间复杂度:O(n)**。<br>
- **空间复杂度:O(h)**,栈的深度同样取决于树高。</p>
<p>这种解法是递归解法的直接迭代翻译,虽然代码不如递归简洁,但有助于理解递归调用的底层机制。</p>
<h2>五、关键细节与常见误区深度剖析</h2>
<p>在理解和实现过程中,需要注意以下关键点:</p>
<p><strong>1. 深度定义与返回值</strong>:<br>
本题定义的深度是“节点数”。因此,空树(`root == null`)的深度是0。在递归解法中,`return 0` 是递归基;在BFS中,如果根为空,应直接返回0,否则从深度1开始计数。这是与“边数”定义的主要区别。</p>
<p><strong>2. 递归的“+1”位置</strong>:<br>
在递归公式中,`+1` 代表当前节点自身贡献的一层深度。它加在左右子树最大深度的比较结果之后,逻辑上是:当前节点深度 = 子树中更深的那个深度 + 1。</p>
<p><strong>3. BFS中 `levelSize` 的妙用</strong>:<br>
在BFS的循环中,必须先获取当前队列的大小并保存在 `levelSize` 中,然后根据这个固定值进行内层循环。这是因为在内层循环中,我们会不断从队列中取出节点并向队列中加入其子节点。如果不事先固定 `levelSize`,队列的大小会在内层循环中动态变化,导致无法区分层与层的边界,从而错误计算深度。</p>
<p><strong>4. 复杂度分析中的平衡与不平衡树</strong>:<br>
对于递归和DFS迭代,空间复杂度高度依赖于树的高度(h)。在<strong>平衡二叉树</strong>中,h ≈ log₂(n),空间效率很高;在<strong>退化成链表的树</strong>中,h = n,空间效率最差。而BFS的空间复杂度则取决于最大宽度,在平衡树中,最底层宽度约为 n/2,因此空间复杂度可能更高。选择解法时需根据树的特点权衡。</p>
<h2>六、举一反三:从最大深度到相关树问题</h2>
<p>掌握<strong>LeetCode二叉树的最大深度</strong>的解法后,可以轻松解决一系列相关的二叉树深度/高度问题,它们共享相似的思想。</p>
<p><strong>1. 二叉树的最小深度(LeetCode 111)</strong>:<br>
注意:最小深度是从根节点到<strong>最近叶子节点</strong>的节点数。不能简单地将 `max` 改为 `min`,因为如果一个节点只有左子树或只有右子树,它的最小深度不是1,而是那棵唯一子树的最小深度+1。这需要额外的条件判断。</p>
<p><strong>2. 平衡二叉树的判断(LeetCode 110)</strong>:<br>
需要计算每个节点的左右子树高度差。可以在计算高度的递归过程中,同时判断是否平衡,避免重复计算。这通常通过一个返回值为 `int`(高度)或自定义类(包含高度和是否平衡)的递归函数实现。</p>
<p><strong>3. 二叉树的直径(LeetCode 543)</strong>:<br>
直径是任意两个节点间最长路径的边数。这条路径可能不经过根节点。关键在于:对于每个节点,以其为“最高点”的路径长度 = 左子树高度 + 右子树高度。在计算节点高度的递归过程中,可以同时更新这个最大值。</p>
<p><strong>4. N叉树的最大深度(LeetCode 559)</strong>:<br>
将二叉树的递归思想推广到N个孩子。递归公式变为:`maxDepth(root) = 1 + max(maxDepth(child1), maxDepth(child2), ..., maxDepth(childN))`。</p>
<p>在鳄鱼Java社区的树算法专题中,这些问题构成了一个完整的知识网络,都是从基础的高度计算衍生而来的。</p>
<h2>总结与思考</h2>
<p><strong>LeetCode二叉树的最大深度</strong>是一道经典的入门题,但它像一颗多棱镜,从不同角度折射出算法设计的多样性与美感。递归解法展现了分治思想的简洁与深刻;BFS迭代体现实用主义的清晰与稳健;DFS迭代则揭示了递归与栈的底层联系。</p>
<p>现在,请你思考:对于你当前的项目或工作中遇到的树形结构数据(如文件目录、组织架构、菜单权限树),你是否能迅速识别出需要计算“最大深度”或类似指标的场景?当面对更复杂的约束时(如计算加权深度、忽略某些特殊节点),你能否基于今天掌握的递归或遍历框架,灵活地修改算法以满足需求?真正的掌握,是将“深度优先”与“广度优先”的思维模式,内化为你解决复杂层级关系问题的直觉。如果你想进一步探索树与图算法在真实系统设计中的应用,欢迎持续关注鳄鱼Java社区的算法与数据结构专栏,我们将一起深入更广阔的算法世界。</p>
版权声明
本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。





