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

相关推荐
ji198594433 分钟前
局部线性嵌入(LLE)算法 MATLAB 实现
算法·机器学习·matlab
Deepoch8 分钟前
Deepoc VLA开发板:无人机群体协同与无网络自主作业核心
网络·人工智能·算法·无人机·deepoc·具身模型开发板
随意起个昵称15 分钟前
线性dp-计数类题目11(不等数列)
c++·算法·动态规划
Black蜡笔小新23 分钟前
自动化AI算法训练服务器DLTM零代码私有化部署筑牢企业AI落地根基
人工智能·算法·自动化
wWYy.24 分钟前
算法:最大子数组和
算法
吃着火锅x唱着歌28 分钟前
LeetCode 3829.设计共享出行系统
算法·leetcode·职场和发展
炸薯条!30 分钟前
二叉树的链式表示
数据结构·算法
CHHH_HHH31 分钟前
【C++】二叉搜索树全面升级,深度剖析AVL树
开发语言·数据结构·c++·算法·stl
Mumu121832 分钟前
P3211 [HNOI2011] XOR和路径
算法
高一学习c++会秃头吗33 分钟前
页面置换算法实现
算法