【力扣100题】30.二叉树的直径

一、题目描述

给你一棵二叉树的根节点 root,返回该树的 直径。

二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root。

两节点之间路径的 长度 由它们之间 边数 表示。

示例

示例 输入 输出 路径说明
示例1 root = [1,2,3,4,5] 3 路径 [4,2,1,3] 或 [5,2,1,3]
示例2 root = [1,2] 1 路径 [1,2]
复制代码
示例1的树结构:
        1
       / \
      2   3
     / \
    4   5

最长路径:4 → 2 → 1 → 3,边数为3
注意:不是经过根节点的路径才最长!

提示

  • 树中节点数目在范围 [1, 10^4] 内
  • -100 <= Node.val <= 100

二、解题思路总览

方法 核心思想 时间复杂度 空间复杂度
递归 后序遍历,计算每个节点的"左右深度和" O(n) O(h),h为树高

关键 insight:

  • 经过某个节点的最长路径 = 左子树深度 + 右子树深度 + 1(节点数)
  • 直径 = 所有节点中最大的"左右深度和 + 1"减1(因为直径是边数不是节点数)

核心公式:

复制代码
经过节点x的最长路径 = 左子树深度 + 右子树深度 + 1
直径 = max(所有节点的左子树深度 + 右子树深度) + 1 - 1

三、完整代码

cpp 复制代码
class Solution {
public:
    int ans = 1;                              // 记录最大直径(节点数)

    int traversal(TreeNode* root) {
        if (!root) return 0;                  // 1. 空节点,深度为0

        int l = traversal(root->left);       // 2. 左子树深度
        int r = traversal(root->right);      // 3. 右子树深度

        // 4. 更新最大直径(节点数)
        // l + r + 1:左子树深度 + 右子树深度 + 当前节点
        ans = max(ans, l + r + 1);

        // 5. 返回当前节点的深度(用于父节点计算)
        // 深度 = max(左子树深度, 右子树深度) + 1
        return max(l, r) + 1;
    }

    int diameterOfBinaryTree(TreeNode* root) {
        traversal(root);                       // 6. 递归遍历所有节点
        return ans - 1;                        // 7. 直径 = 节点数 - 1 = 边数
    }
};

四、算法流程图(ASCII)

示例1的树结构

复制代码
        1
       / \
      2   3
     / \
    4   5

计算过程:

    traversal(1)
        │
        ├── traversal(2)
        │       │
        │       ├── traversal(4) → return 1 (叶子节点)
        │       │
        │       ├── traversal(5) → return 1 (叶子节点)
        │       │
        │       ├── ans = max(1, 1+1+1) = max(1, 3) = 3
        │       └── return max(1,1) + 1 = 2
        │
        ├── traversal(3)
        │       │
        │       ├── traversal(NULL) → return 0
        │       ├── traversal(NULL) → return 0
        │       ├── ans = max(3, 0+0+1) = 3
        │       └── return max(0,0) + 1 = 1
        │
        ├── ans = max(3, 2+1+1) = max(3, 4) = 4  ← 这里其实不会变
        └── return max(2, 1) + 1 = 3

最终:ans = 4(节点数),直径 = ans - 1 = 3

直径与深度的关系

复制代码
直径路径可能不经过根节点:

        1
       / \
      2   3
     / \
    4   5

路径 [4,2,5] 不经过根节点:
- 经过节点2的路径 = 深度4(1) + 深度5(1) + 1(节点2) = 3个节点
- 直径 = 3 - 1 = 2条边

路径 [4,2,1,3] 经过根节点:
- 经过节点1的路径 = 深度2(2) + 深度3(1) + 1(节点1) = 4个节点
- 但这不是最长的!

五、逐行解析

cpp 复制代码
class Solution {
public:
    // ─────────────────────────────────────────
    // 全局变量ans:记录遍历过程中遇到的最大直径(节点数)
    // 初始化为1:至少有一个节点时,直径至少是1个节点
    // ─────────────────────────────────────────
    int ans = 1;

    // ─────────────────────────────────────────
    // 后序遍历:返回以root为根的子树的深度
    // 同时更新全局变量ans
    // ─────────────────────────────────────────
    int traversal(TreeNode* root) {
        // ─────────────────────────────────────────
        // 第1步:递归终止条件
        // 空节点深度为0
        // ─────────────────────────────────────────
        if (!root) return 0;

        // ─────────────────────────────────────────
        // 第2步:递归计算左子树深度
        // 后序遍历:先处理左子树
        // ─────────────────────────────────────────
        int l = traversal(root->left);

        // ─────────────────────────────────────────
        // 第3步:递归计算右子树深度
        // 后序遍历:再处理右子树
        // ─────────────────────────────────────────
        int r = traversal(root->right);

        // ─────────────────────────────────────────
        // 第4步:更新最大直径(节点数)
        //
        // 经过当前root的最长路径 = 左深度 + 右深度 + 1
        // l:左子树深度(边数)
        // r:右子树深度(边数)
        // +1:当前root节点
        //
        // ans记录所有节点中最大的那个值
        // ─────────────────────────────────────────
        ans = max(ans, l + r + 1);

        // ─────────────────────────────────────────
        // 第5步:返回当前节点的深度
        //
        // 当前节点的深度 = max(左子树深度, 右子树深度) + 1
        // +1:从子节点到当前节点的边
        //
        // 这个返回值会给父节点使用
        // 父节点计算路径时需要知道子树的深度
        // ─────────────────────────────────────────
        return max(l, r) + 1;
    }

    // ─────────────────────────────────────────
    // 入口函数:返回直径(边数)
    // ─────────────────────────────────────────
    int diameterOfBinaryTree(TreeNode* root) {
        traversal(root);      // 遍历所有节点,更新ans
        return ans - 1;       // ans是节点数,直径是边数,所以-1
    }
};

六、复杂度分析

时间复杂度

分析 复杂度
每个节点访问一次 O(n)

推导:递归遍历每个节点一次,每个节点做常数次操作。

空间复杂度

分析 复杂度
函数调用栈,最大深度为树高h O(h)

推导:

  • 最坏情况(链表形状):h = n,复杂度 O(n)
  • 平衡树情况:h = log n,复杂度 O(log n)

七、面试追问 FAQ

问题 回答
为什么返回 ans - 1? ans 记录的是最长路径的节点数 ,直径是边数,边数=节点数-1
直径一定经过根节点吗? 不一定!如示例1,直径是 [4,2,5] 或 [4,2,1,3],前者不经过根
如何理解 l + r + 1? 左子树深度(边)+ 右子树深度(边)+ 当前节点 = 经过当前节点的最长路径的节点数
为什么不叫"深度"而叫"直径"? 树的直径是图论概念,指任意两点间的最长路径
和二叉树最大深度有什么区别? 最大深度是从根到叶子的最长路径,直径是任意两节点之间

八、相关题目

题目 难度 关键点
543. 二叉树的直径 简单 本题
104. 二叉树的最大深度 简单 递归求深度
110. 平衡二叉树 简单 计算高度
124. 二叉树中的最大路径和 困难 扩展本题

九、总结

对比项 说明
代码行数 核心7行
时间复杂度 O(n)
空间复杂度 O(h)
递归顺序 后序遍历(左-右-根)
核心变量 ans:最大直径(节点数),l/r:子树深度

核心理解:

复制代码
直径 = max(左子树深度 + 右子树深度) + 1 - 1
     = max(所有节点的左深度 + 右深度)

返回值 = max(左子树深度, 右子树深度) + 1

易错点:

  • 直径是边数,不是节点数
  • 直径可能不经过根节点
  • ans 初始化为1(而不是0)因为单个节点也算一条"路径"

相关推荐
垆边人似月.19 分钟前
华为题:敏感词替换
算法
0+11128 分钟前
算法 --哈希表
数据结构·算法·散列表
Interview Aid1121 小时前
Stripe VO 面经汇总:5 轮面试题目与考察重点
算法
EEET智选1 小时前
科技晨间速报 | AI 安全警钟、开源新模型与苹果智能家居
人工智能·科技·算法·机器学习·ai
挖掘狂人1 小时前
哈希算法:一根看不见的 "指纹",如何撑起整个互联网
算法·程序员·掘金技术征文
鶴哥只手遮天1 小时前
ecRad 1.5.0 算法原理详解
算法·开源
不会就选b2 小时前
算法日常・每日刷题--<动态规划>22
算法·动态规划
释厄6232 小时前
字画同体论——字即画·几何即字·漫画与小说=文本层
人工智能·算法
沫璃染墨2 小时前
从零入门数据结构与算法(一):复杂度分析——从算法效率到时间与空间复杂度
c语言·开发语言·数据结构·笔记·算法·飞书
熊猫钓鱼>_>3 小时前
面试出现 606 次的第 K 大,被拆成三个问题问
算法·面试·职场和发展·校招·面经·oj·ai生成