LeetCode基础题第104题「二叉树的最大深度」,这道题是二叉树遍历的经典入门题,核心考察对二叉树层次遍历(BFS)的理解和应用,适合新手入门练手。今天就来详细拆解这道题,从题目理解到代码实现,再到细节优化,一步步讲清楚,看完就能轻松掌握。
一、题目解读
题目描述
给定一个二叉树 root ,返回其最大深度。二叉树的最大深度是指从根节点到最远叶子节点的最长路径上的节点数。
简单来说,就是要找到二叉树"最深"的那一层,统计从根到这一层的所有节点个数。举个例子:
-
如果二叉树只有根节点,没有左右子节点,最大深度就是1;
-
如果根节点有左子节点,左子节点又有一个子节点,那么最大深度就是3;
-
如果二叉树是空树(root为null),最大深度就是0。
核心考点
这道题的核心是「二叉树的遍历」,常见的解法有两种:
-
层次遍历(BFS,广度优先搜索):按层遍历二叉树,每遍历完一层,深度加1,最终的深度就是最大深度(本文重点讲解这种解法,对应给出的代码);
-
深度优先搜索(DFS):递归遍历左右子树,取左右子树的最大深度,再加上当前根节点的深度1,即为整个树的最大深度(后续会补充备选代码)。
二、代码解析(TypeScript)
先贴出完整代码(已优化,解决原代码潜在问题),再逐行拆解思路,新手也能看懂~
typescript
/**
* Definition for a binary tree node.
* 二叉树节点的定义(题目已给出,无需修改)
*/
class TreeNode {
val: number
left: TreeNode | null
right: TreeNode | null
constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
this.val = (val === undefined ? 0 : val)
this.left = (left === undefined ? null : left)
this.right = (right === undefined ? null : right)
}
}
/**
* 计算二叉树最大深度(层次遍历/BFS解法)
* @param root 二叉树根节点
* @returns 二叉树的最大深度
*/
function maxDepth(root: TreeNode | null): number {
// 边界处理:空树直接返回深度0(避免后续无效循环)
if (root === null) {
return 0;
}
let depth = 0; // 记录当前深度,初始化为0
// 用数组模拟队列,存储当前层的所有节点(初始存入根节点)
const queue: TreeNode[] = [root];
// 队列不为空,说明还有节点未遍历,继续循环
while (queue.length > 0) {
const levelSize = queue.length; // 当前层的节点个数
// 遍历当前层的所有节点
for (let i = 0; i < levelSize; i++) {
// 取出当前层的节点(优化:用pop+unshift替代shift,提升性能)
const node = queue.pop()!;
// 若当前节点有右子节点,存入队列(先存右,再存左,保证遍历顺序)
if (node.right) {
queue.unshift(node.right);
}
// 若当前节点有左子节点,存入队列
if (node.left) {
queue.unshift(node.left);
}
}
// 可选:打印队列,观察每一层遍历后的节点变化(调试用)
// 当前层遍历完毕,深度加1
depth++;
}
return depth;
}
逐行拆解思路
1. 边界处理(关键!)
if (root === null) { return 0; }
这一步是很多新手容易忽略的点:如果二叉树是空树(root为null),没有任何节点,最大深度自然是0。如果不做这个判断,后续队列会存入null,导致循环多执行一次,最终返回错误的深度1。
2. 初始化变量
-
let depth = 0;:用于记录二叉树的深度,初始值为0(因为还未开始遍历任何一层); -
const queue: TreeNode[] = [root];:用数组模拟队列(队列是BFS的核心数据结构),初始时将根节点存入队列,代表从根节点开始遍历。
3. 层次遍历核心循环
while (queue.length > 0) { ... }:队列不为空,说明还有节点未遍历,循环继续。每一次循环,都代表遍历完「一层」节点。
4. 遍历当前层节点
const levelSize = queue.length;:获取当前层的节点个数,这个值是固定的(因为后续队列会存入下一层的节点,不能直接用queue.length判断当前层节点数)。
for (let i = 0; i < levelSize; i++) { ... }:循环遍历当前层的每一个节点,把每个节点的左右子节点(如果有的话)存入队列,为下一层遍历做准备。
5. 节点取出与子节点入队(性能优化点)
原代码中如果用queue.shift()取出节点,会有性能问题------因为数组的shift()方法是O(n)时间复杂度(需要将数组中所有元素向前移动一位),当二叉树节点较多时,效率会很低。
优化方案:用queue.pop()(O(1)时间复杂度)取出队列尾部的节点,同时调整子节点入队顺序(先存右子节点,再存左子节点),保证遍历顺序和shift()一致,既提升性能,又不影响结果。
这里的!是非空断言,因为我们已经通过边界处理和循环条件,确保队列中的节点一定是TreeNode类型(不会为null),所以可以安全使用非空断言。
6. 深度递增
depth++;:每遍历完一层,说明二叉树的深度增加了1,所以深度加1。当循环结束时,depth就是二叉树的最大深度。
三、代码优化说明
对比LeetCode题目给出的初始模板,这段代码做了3个关键优化,兼顾性能和正确性:
-
新增边界处理:解决空树返回错误深度的问题,让代码更健壮;
-
性能优化:用
pop() + unshift()替代shift(),将节点取出操作的时间复杂度从O(n)降至O(1),适合处理节点较多的二叉树;
可读性优化:添加详细注释,变量命名语义化(如levelSize代表当前层节点数),方便新手理解每一步的执行过程。
四、备选解法(DFS递归版)
除了上述层次遍历(BFS)解法,这道题还可以用深度优先搜索(DFS)的递归写法,代码更简洁,适合树深度不大的场景(避免递归栈溢出),新手也可以了解一下:
typescript
function maxDepthRecursive(root: TreeNode | null): number {
// 空树返回0
if (root === null) {
return 0;
}
// 递归计算左右子树的最大深度,当前深度 = 左右子树最大深度 + 1(当前节点)
return Math.max(maxDepthRecursive(root.left), maxDepthRecursive(root.right)) + 1;
}
递归解法的核心思路:二叉树的最大深度 = 左子树最大深度和右子树最大深度的最大值 + 1(当前根节点),本质是遍历到最底层的叶子节点,再回溯计算深度。
五、总结
LeetCode 104题是二叉树遍历的入门题,难度简单,但能很好地巩固BFS和DFS的基础思路。本文讲解的层次遍历(BFS)解法,适合所有场景(包括极深的二叉树,避免递归栈溢出),优化后的代码兼顾性能和可读性,新手可以重点掌握。
解题关键记住两点:
-
边界处理:空树直接返回0,避免错误;
-
层次遍历核心:用队列存储每一层的节点,每遍历完一层,深度加1。