[从零开始面试算法] (06/100) LeetCode 104. 二叉树的最大深度:递归的“分解”艺术

引言

欢迎来到本系列的第六篇!告别了线性的数组和链表,今天我们将踏入一个更广阔、更具层次感的数据结构世界——树 (Tree),并从它的最基本形态——二叉树 (Binary Tree) 开始探索。

二叉树是面试中名副-其实的“明星”,其相关问题层出不穷。为了稳固地开启我们的二叉树之旅,我们将从最经典、最基础的问题 LeetCode 104. 二叉树的最大深度 入手。

第一次看到这个问题,你可能会尝试通过数学公式,从节点总数来推算深度。但你会很快发现,这个方法只适用于“完美”的树。那么,对于任意形状的二叉树,我们该如何求解呢?

本文将带你一起:

  1. 分析“数学公式法”的局限性,理解为何它不适用于所有二叉树。

  2. 深入理解递归思想如何与树的“分形”结构完美契合。

  3. 掌握求解树的深度的“黄金法则”,并写出极其简洁优雅的递归代码。

一、题目与前置知识

  • 题目编号:104. 二叉树的最大深度

  • 题目难度:简单

  • 题目链接104. 二叉树的最大深度 - 力扣 (LeetCode)

  • 题目描述

    给定一个二叉树,找出其最大深度。

    二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

    说明: 叶子节点是指没有子节点的节点。

  • 示例

    • 输入: root = [3,9,20,null,null,15,7] (对应下图)

    • 输出: 3

            3
           / \
          9   20
             /  \
            15   7

前置知识:二叉树节点定义

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

二、我的思考:直觉思路的“陷阱”

看到“深度”,我的第一反应是它和“节点总数”之间应该有某种数学关系。对于一个每层都铺满的“完美”二叉树(满二叉树),其节点数和深度的关系是明确的。例如,深度为 3 的满二叉树有 2^3 - 1 = 7 个节点。

于是,我最初的想法是:能不能通过总节点数,反推出树的深度?

这个思路的困境:
这个方法对于形态规整的树是有效的,但对于任意形状的二叉树则完全行不通。思考一个极端的“链式”二叉树:

    1
     \
      2
       \
        3

这棵树只有 3 个节点,如果按照完全二叉树的公式计算,深度应该是 2。但它的实际最大深度却是 3!

结论:我们无法依赖一个简单的数学公式。我们需要一种能够适应树的任意结构的方法。这正是递归大显身手的地方。


三、豁然开朗:递归的“分解”艺术

树形问题天生就适合用递归来解决,因为树本身就是一个递归定义的数据结构:一棵树,是由一个根节点和它的左、右两棵子树组成的。

我们可以利用这个特性,将一个“求整棵树的深度”的大问题,分解成“求其子树深度”的小问题。

求解最大深度的“黄金法则”

一棵树的最大深度 = 1 (根节点本身占一层) + 其左右子树中,深度较大的那个的深度。

让我们用这个法则来手动求解示例中的树:

      3
     / \
    9   20
       /  \
      15   7

  1. 求 maxDepth(根节点 3) 的深度

    • 根据法则,它等于 1 + max( maxDepth(左子树 9), maxDepth(右子树 20) )。

  2. 分解问题,先求子问题

    • 求 maxDepth(左子树 9):

      • 节点 9 是叶子节点,它的左右子树都是空 (null)。

      • maxDepth(9) = 1 + max( maxDepth(null), maxDepth(null) )

      • 我们定义空树的深度为 0

      • 所以,maxDepth(9) = 1 + max(0, 0) = 1。

    • 求 maxDepth(右子树 20):

      • maxDepth(20) = 1 + max( maxDepth(15), maxDepth(7) )。

      • maxDepth(15) 和 maxDepth(7) 都是叶子节点,它们的深度计算出来也都是 1。

      • 所以,maxDepth(20) = 1 + max(1, 1) = 2。

  3. 汇总结果

    • 现在我们有了子问题的答案:左子树深度为 1,右子树深度为 2。

    • 代入第一步的公式:maxDepth(3) = 1 + max(1, 2) = 3。

我们得到了正确答案!这个“分解-求解-汇总”的过程,就是递归的精髓。


四、C++ 最优代码与详解

这个递归思路可以被直接、优雅地翻译成代码。

完整代码
#include <algorithm> // 为了使用 std::max

class Solution {
public:
    int maxDepth(TreeNode* root) {
        // 1.
        if (root == nullptr) {
            return 0;
        }

        // 2.
        int left_depth = maxDepth(root->left);
        int right_depth = maxDepth(root->right);

        // 3.
        return 1 + std::max(left_depth, right_depth);
    }
};

代码逐点解释
  1. if (root == nullptr)
    这是递归的终止条件 (Base Case)。当递归到空节点时(比如一个叶子节点的子节点),我们知道空树的深度为 0,直接返回。这是递归能够停止并开始“回归”的关键。

  2. int left_depth = maxDepth(root->left); int right_depth = maxDepth(root->right);
    这是问题的分解。我们不关心左右子树内部有多复杂,我们只是信任地调用 maxDepth 函数自身去解决这两个规模更小的子问题。函数会一路递归下去,直到遇到终止条件,然后逐层返回子问题的解。

  3. return 1 + std::max(left_depth, right_depth);
    这是结果的汇总。当两个子问题 maxDepth(root->left) 和 maxDepth(root->right) 都返回了它们的答案后,当前层就根据我们的“黄金法则”来计算自己的深度:1 (当前层) + 左右子深度中的较大值,然后将这个结果返回给它的上一层调用。


五、总结与收获

  • 复杂度分析:我们访问了树中的每一个节点一次,因此时间复杂度为 O(n),其中 n 是节点的数量。递归调用栈的深度在最坏情况下(链式树)等于树的高度,因此空间复杂度为 O(h),其中 h 是树的高度。

  • 核心思想递归是解决树形问题的首选“瑞士军刀”。通过定义好终止条件递归关系,我们可以将复杂的大问题分解为简单的子问题来求解。

  • 关键技巧:求解树的高度/深度问题的核心公式是 1 + max(左子树高度, 右子树高度)。这个模式在许多其他树形问题中也会反复出现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值