📌 题目链接:543. 二叉树的直径 - 力扣(LeetCode)
🔍 难度:简单 | 🏷️ 标签:树、深度优先搜索(DFS)、递归、树形 DP
⏱️ 目标时间复杂度:O(N)
💾 空间复杂度:O(Height) (递归栈深度)
题目分析
问题本质:求一棵二叉树中任意两个节点之间的最长路径(以边数计)。
注意:这条路径不一定经过根节点!
例如:
- 输入
[1,2,3,4,5],最长路径是4 → 2 → 1 → 3或5 → 2 → 1 → 3,共 3 条边,所以输出3。 - 路径长度 = 节点数 - 1。
关键洞察 ✨:
任意一条路径,必然存在一个“最高点”(即路径的转折点),该路径由其左子树的一条向下路径 + 右子树的一条向下路径拼接而成。
因此,我们可以对每个节点计算:以它为“最高点”的最长路径 = 左子树最大深度 + 右子树最大深度。
然后在所有节点中取最大值即可。
核心算法及代码讲解
🧠 核心思想:后序遍历 + 全局最大值更新
我们使用 深度优先搜索(DFS) 后序遍历整棵树,在访问每个节点时:
-
递归获取左子树的最大深度
L -
递归获取右子树的最大深度
R -
当前节点作为路径“最高点”时,路径上的节点数为
L + R + 1- 因此路径长度(边数)为
L + R
- 因此路径长度(边数)为
-
用全局变量
ans记录最大的(L + R + 1)(即最大节点数) -
函数返回当前子树的深度:
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函数执行的是后序遍历:先处理左右子树,再处理当前节点。- 这不是单纯的求深度,而是在求深度的过程中顺带计算了以每个节点为中心的最长路径。
解题思路(分步拆解)
-
理解“直径”定义:任意两节点间的最长路径(边数),不强制过根。
-
观察路径结构:任何路径都有唯一“最高点”(最近公共祖先),路径 = 左链 + 右链。
-
转化问题:对每个节点,计算
左深度 + 右深度,取最大值。 -
设计递归函数:
- 功能:返回子树深度
- 副作用:更新全局最大直径(通过
L + R)
-
后序遍历实现:确保子问题先解决,父问题才能合并结果。
-
边界处理:空节点深度为 0。
-
结果转换:最大节点数 - 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!💪
📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!