【力扣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)因为单个节点也算一条"路径"

相关推荐
gihigo19982 小时前
Bezier曲线曲面生成算法
算法
平行侠2 小时前
024多精度大整数 - 突破硬件精度限制的任意精度运算
数据结构·算法
IronMurphy3 小时前
【算法四十五】139. 单词拆分
算法
洛水水4 小时前
【力扣100题】32.将有序数组转换为二叉搜索树
数据结构·算法·leetcode
如竟没有火炬4 小时前
用队列实现栈
开发语言·数据结构·python·算法·leetcode·深度优先
云栖梦泽在5 小时前
AI安全入门:AI模型泄露的风险与防护措施
人工智能·算法·动态规划
水木流年追梦5 小时前
大模型入门-应用篇3-Agent智能体
开发语言·python·算法·leetcode·正则表达式
凯瑟琳.奥古斯特5 小时前
假脱机技术原理详解
开发语言·职场和发展
洛水水5 小时前
【力扣100题】31.二叉树的层序遍历
算法·leetcode·职场和发展