题目描述
给定一个二叉树的根节点 root,返回它的最大深度。
二叉树的最大深度是指从根节点到最远叶子节点的最长路径上的节点数。
核心解题思路
求二叉树的最大深度,本质上是一个自底向上或自顶向下的统计过程。在算法面试中,通常有两种最经典的解法:
方法一:递归法(深度优先搜索 DFS)
这是二叉树最优雅、最符合直觉的解法。核心思想是分治:
一棵树的最大深度,取决于它左子树和右子树谁更深。
公式推导:
当前树的深度 = max(左子树深度, 右子树深度) + 1(加 1 代表当前根节点自身占一层)
递归三要素拆解:
- 确定终止条件:如果当前节点为空(
root == null),说明已经走到了叶子节点的下方,这条路径的深度为 0,直接返回。 - 确定单层递归逻辑:分别递归调用函数,求出左子树的深度和右子树的深度。
- 确定返回值:比较左右子树的深度,取较大值,然后加上当前节点的 1 层,返回给上一层。
方法二:迭代法(广度优先搜索 BFS / 层序遍历)
如果不想使用递归,或者担心树太深导致系统栈溢出,可以使用迭代法。
核心思想是逐层剥洋葱:
我们不需要关心具体的路径,只需要知道这棵树一共有多少层。每遍历完一层,深度就
+1,直到队列为空。
JavaScript 代码实现
1. 递归实现(极简且高效)
var maxDepth = function(root) {
// 1. 递归终止条件:空节点深度为 0
if (!root) return 0;
// 2. 分别递归计算左、右子树的最大深度
const leftDepth = maxDepth(root.left);
const rightDepth = maxDepth(root.right);
// 3. 取左右子树深度的最大值,加上当前根节点的 1 层
return Math.max(leftDepth, rightDepth) + 1;
};
2. 迭代实现(层序遍历 BFS)
var maxDepth = function(root) {
if (!root) return 0;
const queue = [root]; // 初始化队列,放入根节点
let depth = 0; // 记录深度
// 只要队列中还有节点,就说明还有层需要遍历
while (queue.length > 0) {
// 关键:提前记录当前层的节点数量
// 因为在 for 循环中,我们会不断往队列里塞下一层的节点,length 是动态变化的
const levelSize = queue.length;
depth++; // 进入新的一层,深度加 1
// 遍历当前层的所有节点
for (let i = 0; i < levelSize; i++) {
const node = queue.shift(); // 取出当前节点
// 如果左孩子不为空,加入队列(留给下一层处理)
if (node.left) queue.push(node.left);
// 如果右孩子不为空,加入队列
if (node.right) queue.push(node.right);
}
}
return depth;
};
复杂度分析
-
时间复杂度: O(n)O(n) 。无论是递归还是迭代,树中的每一个节点都恰好被访问一次,其中 nn 为节点总数。
-
空间复杂度:
- 递归法: O(h)O(h) , hh 为树的高度。空间消耗在系统隐式的递归调用栈上。最坏情况(树退化为链表)为 O(n)O(n) ,最好情况(完全平衡二叉树)为 O(logn)O(logn) 。
- 迭代法: O(w)O(w) , ww 为树的最大宽度。空间消耗在我们手动维护的队列上。最坏情况(完全平衡二叉树的最后一层)约为 O(n/2)O(n/2) ,即 O(n)O(n) 。
细腻点拨:面试加分项
在面试中写完代码后,你可以主动补充:
- 为什么选递归:“这道题具有天然的子问题结构,后序遍历(先算左右子树,再算根节点)非常契合求深度的需求,代码可读性最高。”
- BFS 的适用场景:“如果面试官追问‘如果树极度不平衡,比如是一条长达十万个节点的链表,递归会栈溢出怎么办?’,此时可以抛出 BFS 迭代法,因为迭代法使用的是堆内存中的队列,不受系统调用栈深度的限制。”