先记住这个答案
求二叉树最大深度即树高,按节点数计算。自底向上写法采用后序:若节点为空返回0,否则返回左右子树最大深度的较大值加1,代表根节点本身。自顶向下则维护当前深度变量,每向下一层加1,当节点为叶子或无孩子时用当前深度更新全局最大值。两者时间均为O(n),空间取决于树高,最差为链状O(n),平衡时为O(log n)。面试时需明确深度定义是节点数而非边数,空树深度为0。
- 最大深度等效树高,按节点计数
- 自底向上用后序返回子树高度
- 自顶向下用前序维护当前深度
两种递归写法的核心差异
自底向上的递归式可写成 depth(null)=0,非空节点 depth(r)=max(depth(r.left),depth(r.right))+1。它模仿后序遍历,先探明孩子再决策当前,任何子树的计算结果独立。三节点左链的调用会依次返回1、2、3,每次把左结果+1。
自顶向下则把深度当作参数,调用时 traverse(node, level),下一层传 level+1。代码中每次访问节点都会用当前深度更新全局最大值(若更深则覆盖),即所有非空节点都触发比较。该遍历类似前序,可在递归入口就判断是否需先处理本节点,更利于携带路径前缀等额外信息。
function maxDepthBottomUp(root) {
if (root === null) return 0;
return Math.max(maxDepthBottomUp(root.left), maxDepthBottomUp(root.right)) + 1;
}
let bestDepth = 0;
function topDownWalk(node, depth) {
if (node === null) return;
if (depth > bestDepth) bestDepth = depth;
topDownWalk(node.left, depth + 1);
topDownWalk(node.right, depth + 1);
}
function maxDepthTopDown(root) {
bestDepth = 0;
topDownWalk(root, 1);
return bestDepth;
}
const n1 = { val: 1, left: null, right: null };
const leftChain = { val: 1, left: { val: 2, left: { val: 3, left: null, right: null }, right: null }, right: null };
const rightChain = { val: 1, left: null, right: { val: 2, left: null, right: { val: 3, left: null, right: null } } };
const balanced = { val: 1, left: { val: 2, left: { val: 4, left: null, right: null }, right: { val: 5, left: null, right: null } }, right: { val: 3, left: null, right: null } };
const cases = [
{ label: '空树', tree: null },
{ label: '单节点', tree: n1 },
{ label: '左链', tree: leftChain },
{ label: '右链', tree: rightChain },
{ label: '普通平衡树', tree: balanced }
];
for (const c of cases) {
console.log(c.label + ': bottomUp=' + maxDepthBottomUp(c.tree) + ', topDown=' + maxDepthTopDown(c.tree));
}查看输出与解释
空树: bottomUp=0, topDown=0
单节点: bottomUp=1, topDown=1
左链: bottomUp=3, topDown=3
右链: bottomUp=3, topDown=3
普通平衡树: bottomUp=3, topDown=3代码定义两种递归函数,分别模拟后序合并与前序传递。测试覆盖空、单节点、左右链和普通树,输出完全一致。时间复杂度O(n),空间按树高变化。
统计招聘系统组织树的层级上限
假设某行政系统将组织架构以JSON树表示,每个部门节点包含子部门列表,需计算最大深度来决定页面是否直接展开全部。设输入树平均深度8层,最大可达40层,用自底向上递归对每个节点计算,一次遍历得到深度,前端据结果展示'展开'加深度提示。
因数据跑在Node.js服务端,默认递归上限约1万层,本场景最深仅几十层则安全。若预期极端情况(比如导入结构导致树退化为长链)可能很深,可先测量深度,超过阈值改用显式栈迭代,并在栈项中记录当前深度,从而避免爆栈且仍返回准确值。
容易出错的边界条件和处理策略
空树按0返回,因为不存在根节点,但若某些定义按边数则返回-1,差异会传染所有结果。斜树深度等于节点数,递归调用层数与高度相同,当n=10000时可能命中栈限制。此外,若单次调用重用自顶向下函数,忘记重置全局best变量会叠加历史最大值。
正确处理是在递归入口先处理空节点,再用后序或前序计算。对于深度极大场景,可改用显式栈,自顶向下可以存栈项(node,currentDepth);或先做层序遍历用队列记录层号。该代价是代码量增加,但空间是O(n)同样不可避免。
容易答错的地方
- 混淆节点数与边数定义
- 部分面试官把深度定义为边数,此时根深度为0,空树应为-1。若不确认定义,自底向上写出的结果会偏大1,导致后续题目全部偏差。应在开头询问或明确说按节点数计算。
- 全局变量未重置
- 自顶向下实现常依赖一个外部
best变量。如果一次测试后未重置,下一次调用的起点会沿用上次结果,空树也可能返回非0。正确做法是在入口先初始化,或使用局部变量通过返回值收集,减少隐式状态。
面试官还会怎么问?
如果要求返回最大深度对应的叶子节点,递归如何改?
自顶向下在每次到达叶子时比较当前深度与已知最大,若更深入则记录该leaf。自底向上则需返回(深度,叶子)二元组,合并时取较大深度对应的叶子。两种都能在O(n)内完成,但自底向上传递对象略繁琐。
二叉树用数组存储(堆式),如何求最大深度?
若按层序存放于数组,索引i的节点其子节点在2i+1和2i+2(从0起)。可递归模拟索引计算,但数组可能含空位,需判断索引越界或值是否空。复杂度仍O(n),但数组下标加深度可避免显式结构。
如何不用递归求最大深度?
使用显式栈模拟后序:每次弹出节点时已知其左右深度,取大的加1;也可用队列做层序遍历,每遍历一层深度+1。前者需要额外字段或双栈后序,后者直观但队列占用与最宽层成正比。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。