二叉树的最大深度(递归)
Maximum Depth of a Binary Tree (Recursive)
开始编码二叉树的最大深度指从根节点到任意叶子节点的最长路径上的节点数。空树(None)深度为 0;单节点树深度为 1;左偏链 k 个节点深度为 k;完美平衡树 n 个节点深度为 ⌈log2(n+1)⌉。课本式的两行递归 DP 是:
depth(None) = 0
depth(node) = 1 + max(depth(node["left"]), depth(node["right"]))请实现 solution(tree: dict | None) -> int 返回这个深度。tree 用嵌套字典编码:
- 空树:
None。 - 节点:
{"value": <any>, "left": <subtree>, "right": <subtree>},其中每个子树本身也要么是节点 dict 要么是None。
最常见的错误是把叶子节点当作递归基底——别这么干,把 None 子树作为基底(深度 0),叶子节点会自然变成 1 + max(0, 0) = 1。另一个常见错误是把左右子树深度相加而不是取 max;那算的是节点数或者「过根的路径长度」,不是深度。
由于深度上界是 1000,而 Python 默认递归上限也是 1000,恰好打满上限的左偏链会爆栈。请在入口处 sys.setrecursionlimit(2000) 提高上限再开始递归。
示例
solution(None) 返回 0。空树的基底——没有节点,最长 root-to-leaf 路径长度为 0。
solution({"value": 1, "left": None, "right": None}) 返回 1。单节点树:根节点本身就是长度为一的最长路径。
solution({"value": 1, "left": {"value": 2, "left": {"value": 4, "left": None, "right": None}, "right": {"value": 5, "left": None, "right": None}}, "right": {"value": 3, "left": None, "right": None}}) 返回 3。一棵接近平衡的三层树:root → left → leaf 是最长 root-to-leaf 路径;右子树只有一个深度 2 的叶子,被左子树盖过去。
实战背景
深度作为复杂度代理量。在 quant 定价基础设施里,递归树的深度是任何 backward-induction 算法的主导成本因子:n 步二叉树定价器递归深度为 n;一只 30 年期、月度行权的百慕大互换期权定价器递归深度 360;基于回归的美式期权定价器(Longstaff-Schwartz)在样本路径树上走,深度等于行权日数。算清楚深度能告诉你:(a) 该算法是否塞得进可用的栈帧——还是该改成迭代写法,或抬高 recursionlimit;(b) 关键路径上的最坏延迟——每一层深度多一个串行依赖、不可并行;(c) 折现误差累积量——30 步树会累乘 30 次每步折扣因子,每步舍入误差会沿深度放大。同一个 depth 原语还会出现在:决策树求值器、表达式树编译器、依赖图拓扑排序、parser AST 校验器——任何你需要快速在递归对象上做结构性 sanity check 的地方。掌握这个两行递推是掌握之后所有递归树算法的入门钥匙。
约束条件
- 节点数 0 ≤ n_nodes ≤ 10^4。根节点要么是 `None`(空树),要么是一个 dict `{"value": <any>, "left": <subtree>, "right": <subtree>}`,其中每个子树本身也要么是节点 dict 要么是 `None`。
- 最大深度 ≤ 1000。纯递归在最大深度边界(左偏链 1000 节点)会触发 Python 默认递归上限(1000);请在入口处调用 `sys.setrecursionlimit` 抬高到一个安全值(例如 2000),以避免合法的深树打爆栈。
- 返回值为整数。空树(`None`)返回 `0`,单节点树返回 `1`。根节点本身计为深度 1(即深度按**节点数**计,不是按边数)。
- `value` 是任意值,与答案无关——深度只取决于树的形状,不取决于任何节点的内容。
- 评分用整数严格相等比较(exact)。
样例
Case 1 · null tree returns 0
输入: [null]
期望: 0
空树(None)的最大深度是 0——没有节点,最长 root-to-leaf 路径长度为 0。这是最关键的递归基底:实现必须先判 `node is None`,否则后面 `node["left"]` 会立刻 KeyError/TypeError。
Case 2 · single-node tree returns 1
输入: [{"value":7,"left":null,"right":null}]
期望: 1
单节点树的深度为 1(根节点本身计为深度 1,深度按节点数计而非按边数)。从两行递归看:左右子树都是 None 各贡献 0,所以 `1 + max(0, 0) = 1`。这道题钉死「叶子的深度是 1,不是 0」这一约定——避免 off-by-one。
Case 3 · left-skewed chain of length 4
输入: [{"value":1,"left":{"value":2,"left":{"value":3,"left":{"value":4,"left":null,"right":null},"right":null},"right":null},"right":null}]
期望: 4
左偏链 1 → 2 → 3 → 4,每个节点只有左孩子(最后一个是叶子)。深度等于链长 4。这道题验证递归在「右子树永远是 None」时不会少数一层。
Case 4 · perfectly balanced 3-level tree
输入: [{"value":1,"left":{"value":2,"left":{"value":4,"left":null,"right":null},"right":{"value":5,"left":null,"right":null}},"right":{"value":3,"left":{"value":6,"left":null,"right":null},"right":{"value":7,"left":null,"right":null}}}]
期望: 3
满三层二叉树(7 个节点),最大深度 3。两侧子树深度对称——两个 max 分支等价,结果就是 `1 + 2 = 3`。验证 max 在两侧相等时也能正确返回。
Case 5 · LeetCode 104 canonical example [3,9,20,null,null,15,7]
输入: [{"value":3,"left":{"value":9,"left":null,"right":null},"right":{"value":20,"left":{"value":15,"left":null,"right":null},"right":{"value":7,"left":null,"right":null}}}]
期望: 3
LeetCode 104 原题示例:根 3,左孩子是叶子 9(深度贡献 1),右孩子 20 下挂 15 和 7(深度贡献 2)。最长路径走右侧:3 → 20 → 15(或 7),长度 3。这道题钉死实现要在两个子树深度不同时正确取较大者。
Case 6 · lopsided right-heavy tree
输入: [{"value":1,"left":{"value":2,"left":null,"right":null},"right":{"value":3,"left":null,"right":{"value":4,"left":null,"right":{"value":5,"left":null,"right":{"value":6,"left":null,"right":null}}}}}]
期望: 5
左子树是叶子(深度贡献 1),右子树是一条长链 3 → 4 → 5 → 6(深度贡献 4)。`max(1, 4) + 1 = 5`。这道题刻意制造了「左浅右深」的非对称形状,检查实现没有错把 `max` 写成 `min` 或者 `+`。
Case 7 · zigzag chain of depth 5
输入: [{"value":1,"left":{"value":2,"left":null,"right":{"value":3,"left":{"value":4,"left":null,"right":{"value":5,"left":null,"right":null}},"right":null}},"right":null}]
期望: 5
之字形链:1 (左) → 2 (右) → 3 (左) → 4 (右) → 5。每一层向相反方向走,深度为 5。这道题验证递归对左右孩子的处理是对称的——切换方向时不应丢节点。
最近提交
还没有提交记录。
编码区
实现 solution(...)。本地运行当前支持 Python 可见样例;服务端提交会运行可见样例和隐藏测试。
默认展示公开样例。点击「运行样例」后会在这里显示实际输出;点击「提交评测」会进入隐藏测试。
Case 1 · null tree returns 0
输入: [null]
期望: 0
空树(None)的最大深度是 0——没有节点,最长 root-to-leaf 路径长度为 0。这是最关键的递归基底:实现必须先判 `node is None`,否则后面 `node["left"]` 会立刻 KeyError/TypeError。
Case 2 · single-node tree returns 1
输入: [{"value":7,"left":null,"right":null}]
期望: 1
单节点树的深度为 1(根节点本身计为深度 1,深度按节点数计而非按边数)。从两行递归看:左右子树都是 None 各贡献 0,所以 `1 + max(0, 0) = 1`。这道题钉死「叶子的深度是 1,不是 0」这一约定——避免 off-by-one。
Case 3 · left-skewed chain of length 4
输入: [{"value":1,"left":{"value":2,"left":{"value":3,"left":{"value":4,"left":null,"right":null},"right":null},"right":null},"right":null}]
期望: 4
左偏链 1 → 2 → 3 → 4,每个节点只有左孩子(最后一个是叶子)。深度等于链长 4。这道题验证递归在「右子树永远是 None」时不会少数一层。
Case 4 · perfectly balanced 3-level tree
输入: [{"value":1,"left":{"value":2,"left":{"value":4,"left":null,"right":null},"right":{"value":5,"left":null,"right":null}},"right":{"value":3,"left":{"value":6,"left":null,"right":null},"right":{"value":7,"left":null,"right":null}}}]
期望: 3
满三层二叉树(7 个节点),最大深度 3。两侧子树深度对称——两个 max 分支等价,结果就是 `1 + 2 = 3`。验证 max 在两侧相等时也能正确返回。
Case 5 · LeetCode 104 canonical example [3,9,20,null,null,15,7]
输入: [{"value":3,"left":{"value":9,"left":null,"right":null},"right":{"value":20,"left":{"value":15,"left":null,"right":null},"right":{"value":7,"left":null,"right":null}}}]
期望: 3
LeetCode 104 原题示例:根 3,左孩子是叶子 9(深度贡献 1),右孩子 20 下挂 15 和 7(深度贡献 2)。最长路径走右侧:3 → 20 → 15(或 7),长度 3。这道题钉死实现要在两个子树深度不同时正确取较大者。
Case 6 · lopsided right-heavy tree
输入: [{"value":1,"left":{"value":2,"left":null,"right":null},"right":{"value":3,"left":null,"right":{"value":4,"left":null,"right":{"value":5,"left":null,"right":{"value":6,"left":null,"right":null}}}}}]
期望: 5
左子树是叶子(深度贡献 1),右子树是一条长链 3 → 4 → 5 → 6(深度贡献 4)。`max(1, 4) + 1 = 5`。这道题刻意制造了「左浅右深」的非对称形状,检查实现没有错把 `max` 写成 `min` 或者 `+`。
Case 7 · zigzag chain of depth 5
输入: [{"value":1,"left":{"value":2,"left":null,"right":{"value":3,"left":{"value":4,"left":null,"right":{"value":5,"left":null,"right":null}},"right":null}},"right":null}]
期望: 5
之字形链:1 (左) → 2 (右) → 3 (左) → 4 (右) → 5。每一层向相反方向走,深度为 5。这道题验证递归对左右孩子的处理是对称的——切换方向时不应丢节点。