LEETCODE 104Easy

二叉树的最大深度

把整棵树的深度拆成子树的深度加一,递归就成了对定义的直接翻译。

问题拆解

题目要的是根节点到最远叶子的路径上有多少个节点。第一反应可能是去枚举所有从根到叶子的路径,比较谁最长,但这样既要记录路径又要回溯,写起来很啰嗦。

真正的关键是换一个视角:一棵树的最大深度,等于它两棵子树中较深那棵的深度,再加上根节点自己这一层。

深度这个量对整棵树的定义,和对任意子树的定义完全一样。这正是递归能生效的信号。

后序递归

先算出左右子树的深度,再合并成当前树的答案——这是典型的后序处理顺序:

public int maxDepth(TreeNode root) {
    if (root == null) return 0;
    return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}
def maxDepth(root):
    if not root:
        return 0
    return 1 + max(maxDepth(root.left), maxDepth(root.right))
func maxDepth(root *TreeNode) int {
    if root == nil {
        return 0
    }
    return 1 + max(maxDepth(root.Left), maxDepth(root.Right))
}
pub fn max_depth(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
    match root {
        None => 0,
        Some(node) => {
            let node = node.borrow();
            1 + Self::max_depth(node.left.clone()).max(Self::max_depth(node.right.clone()))
        }
    }
}

空树的深度是 0,这既是递归的终点,也让叶子节点自然得到 1 + max(0, 0) = 1。整个函数几乎就是把“深度 = 较深子树 + 1”这句话原样抄成了代码。

如果担心极端瘦长的树把递归栈压爆,可以改用层序遍历,一层一层往下数,遍历了几层深度就是几。但对本题的数据规模,递归写法足够清晰。

复杂度

指标 复杂度 原因
时间 O(n) 每个节点恰好被访问一次
空间 O(h) 递归栈深度等于树高,最坏退化成 O(n)

可以迁移的模式

  • 当一个量“对整体的定义”和“对局部的定义”一致时,优先考虑递归;
  • 后序处理适合“答案要靠孩子先算好再汇总”的场景;
  • 空节点返回一个恰当的边界值(这里是 0),能让叶子的计算无需特判。

先想清楚返回值代表什么,再写递归体,往往比先写代码再补语义顺得多。