【LeetCode Hot100 刷题日记 (40/100)】543. 二叉树的直径 —— 树、深度优先搜索(DFS)、递归、树形 DP🌳

27 阅读5分钟

📌 题目链接:543. 二叉树的直径 - 力扣(LeetCode)

🔍 难度:简单 | 🏷️ 标签:树、深度优先搜索(DFS)、递归、树形 DP

⏱️ 目标时间复杂度:O(N)

💾 空间复杂度:O(Height) (递归栈深度)


题目分析

问题本质:求一棵二叉树中任意两个节点之间的最长路径(以边数计)。
注意:这条路径不一定经过根节点!

例如:

  • 输入 [1,2,3,4,5],最长路径是 4 → 2 → 1 → 35 → 2 → 1 → 3,共 3 条边,所以输出 3
  • 路径长度 = 节点数 - 1。

关键洞察 ✨:

任意一条路径,必然存在一个“最高点”(即路径的转折点),该路径由其左子树的一条向下路径 + 右子树的一条向下路径拼接而成。

因此,我们可以对每个节点计算:以它为“最高点”的最长路径 = 左子树最大深度 + 右子树最大深度
然后在所有节点中取最大值即可。


核心算法及代码讲解

🧠 核心思想:后序遍历 + 全局最大值更新

我们使用 深度优先搜索(DFS) 后序遍历整棵树,在访问每个节点时:

  1. 递归获取左子树的最大深度 L

  2. 递归获取右子树的最大深度 R

  3. 当前节点作为路径“最高点”时,路径上的节点数为 L + R + 1

    • 因此路径长度(边数)为 L + R
  4. 用全局变量 ans 记录最大的 (L + R + 1)(即最大节点数)

  5. 函数返回当前子树的深度:max(L, R) + 1

💡 为什么记录 L + R + 1 而不是 L + R
因为深度定义为“从当前节点到最远叶子的节点数”,而路径拼接时包含当前节点本身,所以是 L + R + 1 个节点,对应 L + R 条边。最后 ans - 1 即为答案。

✅ 代码详解(C++)

class Solution {
    int ans; // 全局变量,记录所有节点中 L + R + 1 的最大值(即最大节点数)

    // 返回以 rt 为根的子树的深度(节点数)
    int depth(TreeNode* rt) {
        if (rt == nullptr) {
            return 0; // 空节点深度为 0
        }
        int L = depth(rt->left);   // 左子树深度
        int R = depth(rt->right);  // 右子树深度
        ans = max(ans, L + R + 1); // 更新以当前节点为“最高点”的路径节点数
        return max(L, R) + 1;      // 返回当前子树深度
    }

public:
    int diameterOfBinaryTree(TreeNode* root) {
        ans = 1; // 至少有一个节点,最小路径节点数为 1
        depth(root);
        return ans - 1; // 路径长度 = 节点数 - 1
    }
};

📌 关键点

  • ans 初始化为 1 是因为树至少有一个节点(题目保证 n ≥ 1),此时直径为 0(只有一个节点,无边)。
  • depth 函数执行的是后序遍历:先处理左右子树,再处理当前节点。
  • 这不是单纯的求深度,而是在求深度的过程中顺带计算了以每个节点为中心的最长路径

解题思路(分步拆解)

  1. 理解“直径”定义:任意两节点间的最长路径(边数),不强制过根。

  2. 观察路径结构:任何路径都有唯一“最高点”(最近公共祖先),路径 = 左链 + 右链。

  3. 转化问题:对每个节点,计算 左深度 + 右深度,取最大值。

  4. 设计递归函数

    • 功能:返回子树深度
    • 副作用:更新全局最大直径(通过 L + R
  5. 后序遍历实现:确保子问题先解决,父问题才能合并结果。

  6. 边界处理:空节点深度为 0。

  7. 结果转换:最大节点数 - 1 = 最大边数(即直径)。


算法分析

项目分析
时间复杂度O(N):每个节点仅被访问一次
空间复杂度O(H):H 为树高,递归栈深度。最坏 O(N)(链状树),最好 O(log N)(平衡树)
是否可迭代?理论上可以,但需手动维护栈和左右子树深度信息,代码复杂,递归更清晰
面试考点✅ 树的遍历(后序) ✅ 递归设计 ✅ 全局状态维护 ✅ 路径类问题建模(“最高点”思想)

💬 面试高频追问

  • 如果要求返回具体路径(节点列表)怎么办? → 需要额外记录左右路径,在更新 ans 时保存路径。
  • 能否不用全局变量? → 可以让 depth 返回 {depth, max_diameter_in_subtree},但 C++ 需用 pair,略繁琐。
  • 此方法能否扩展到 N 叉树? → 可以!取深度最大的两个子树相加即可。

代码

✅ C++ 完整可运行代码(含测试)

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

// Definition for a binary tree node.
struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode() : val(0), left(nullptr), right(nullptr) {}
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};

class Solution {
    int ans;
    int depth(TreeNode* rt){
        if (rt == NULL) {
            return 0; // 访问到空节点了,返回0
        }
        int L = depth(rt->left); // 左儿子为根的子树的深度
        int R = depth(rt->right); // 右儿子为根的子树的深度
        ans = max(ans, L + R + 1); // 计算d_node即L+R+1 并更新ans
        return max(L, R) + 1; // 返回该节点为根的子树的深度
    }
public:
    int diameterOfBinaryTree(TreeNode* root) {
        ans = 1;
        depth(root);
        return ans - 1;
    }
};

// 测试
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    // 构建示例1: [1,2,3,4,5]
    TreeNode* root = new TreeNode(1);
    root->left = new TreeNode(2);
    root->right = new TreeNode(3);
    root->left->left = new TreeNode(4);
    root->left->right = new TreeNode(5);

    Solution sol;
    cout << sol.diameterOfBinaryTree(root) << "\n"; // 输出: 3

    // 示例2: [1,2]
    TreeNode* root2 = new TreeNode(1);
    root2->left = new TreeNode(2);
    cout << sol.diameterOfBinaryTree(root2) << "\n"; // 输出: 1

    return 0;
}

✅ JavaScript 版本

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */

/**
 * @param {TreeNode} root
 * @return {number}
 */
var diameterOfBinaryTree = function(root) {
    let ans = 1; // 记录最大节点数

    function depth(node) {
        if (!node) return 0;
        const L = depth(node.left);
        const R = depth(node.right);
        ans = Math.max(ans, L + R + 1);
        return Math.max(L, R) + 1;
    }

    depth(root);
    return ans - 1;
};

🌟 本期完结,下期见!🔥

👉 点赞收藏加关注,新文更新不迷路。关注专栏【算法】LeetCode Hot100刷题日记,持续为你拆解每一道热题的底层逻辑与面试技巧!

💬 欢迎留言交流你的解法或疑问!一起进步,冲向 Offer!💪

📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!