LeetCode 543. 二叉树的直径
题目描述


题意分析
二叉树的直径是任意两个节点之间最长路径的边数,返回这个长度。路径沿父子边连接,不能重复经过同一个节点;起点和终点可以位于任意位置,最长路径不一定经过整棵树的根。
这里计算的是边数,不是节点数,也不是节点值的和。只有一个节点时没有边,直径为
0。从根向下的最长路线只能得到树的高度,而直径还可能从某个节点的左侧经过该节点,继续走向它的右侧。
解法:后序 DFS 计算高度并更新直径
核心思路
[!blue]
任意一条路径都有一个离根最近的节点。把它作为路径的最高点,路径最多由左子树的一条向下路线、当前节点、右子树的一条向下路线组成。枚举每个节点作为最高点,再取所有候选的最大值,就不会漏掉完全位于子树内部的最长路径。
定义
height(node)为从当前节点向下走到最远叶子的节点数。空节点高度为0,非空节点高度为max(leftHeight, rightHeight) + 1。它只返回一侧高度,因为父节点接入后,路径不能再同时向当前节点的左右两侧分叉。当前节点连接左侧最深路线所用的边数,恰好等于
leftHeight:左子树路线内部有leftHeight - 1条边,再加上当前节点连到左孩子的一条边;左子树为空时贡献为0。右侧同理,因此以当前节点为最高点的最长路径边数就是leftHeight + rightHeight。递归先拿到左右高度,用它们的和更新全局
diameter,再把较大高度加一返回给父节点。这样一次后序遍历同时完成“向上提供高度”和“在本层更新直径”两项工作,不必在每个节点重新遍历子树求高度。全局答案初始化为
0。叶子节点左右高度都为零,候选直径自然是零;只有一侧子树时,另一侧的零贡献也能直接套用同一公式。
解题步骤
- 将直径初始化为
0,从根调用height。- 空节点返回高度
0。- 递归求出
leftHeight和rightHeight,先处理孩子,再处理当前节点。- 用
leftHeight + rightHeight更新全局最大直径。- 返回
max(leftHeight, rightHeight) + 1,供父节点计算单侧延伸长度。- 根节点处理完后返回全局直径,而不是根的高度。
代码实现
class Solution {
private int diameter;
public int diameterOfBinaryTree(TreeNode root) {
// 每次调用重新统计,避免复用对象时沿用旧答案。
diameter = 0;
height(root);
return diameter;
}
private int height(TreeNode node) {
if (node == null) {
return 0;
}
int leftHeight = height(node.left);
int rightHeight = height(node.right);
// 左右高度按节点数计算,相加正好得到经当前节点的路径边数。
diameter = Math.max(diameter, leftHeight + rightHeight);
// 向父节点只返回单侧高度,不能把两条分支一起延伸。
return Math.max(leftHeight, rightHeight) + 1;
}
}
func diameterOfBinaryTree(root *TreeNode) int {
ans := 0
var height func(*TreeNode) int
height = func(node *TreeNode) int {
if node == nil {
return 0
}
leftHeight := height(node.Left)
rightHeight := height(node.Right)
// 左右高度按节点数计算,相加正好得到经当前节点的路径边数。
if leftHeight+rightHeight > ans {
ans = leftHeight + rightHeight
}
// 向父节点只返回单侧高度,不能把两条分支一起延伸。
if leftHeight > rightHeight {
return leftHeight + 1
}
return rightHeight + 1
}
height(root)
return ans
}
复杂度分析
- 时间复杂度:$O(n)$,每个节点只计算一次高度并更新一次直径,不重复扫描子树。
- 空间复杂度:$O(h)$,
h为树高,来自递归栈。平衡树为 $O(\log n)$,链状树最坏为 $O(n)$。
关键点总结
[!green]
- 高度按节点数定义,经过当前节点的候选直径按边数计算,公式分别为
max(left, right) + 1和left + right。- 返回值只能延伸一侧,全局答案可以组合两侧,二者不能混用。
- 每个节点都可能成为最长路径的最高点,必须逐节点更新答案。
易错点总结
[!yellow]
- 只在根节点合并左右高度,会漏掉完全位于某棵子树中的最长路径。
- 把候选直径写成
leftHeight + rightHeight + 1,得到的是路径节点数,比边数多一。- 向父节点返回左右高度之和,会把已经连接两侧的路径再接到父节点,形成分叉。
- 返回
height(root)只能得到高度,不能代表任意两节点之间的最大距离。- Java 的成员变量
diameter应在每次入口调用时归零,避免复用同一对象时留下上一次答案。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 124. 二叉树中的最大路径和 | 困难 | 同样在节点处组合左右贡献,本题按边数计长度,原题按节点权值求和并丢弃负贡献。 |
| 687. 最长同值路径 | 中等 | 同样组合两侧延伸长度,原题只允许相邻值相等,本题没有数值限制。 |
| 104. 二叉树的最大深度 | 简单 | 后序返回子树高度并在根处合并信息;本题合并左右向下路径得到直径,该题取两侧最大高度加一。 |
| 110. 平衡二叉树 | 简单 | 后序返回子树高度并在根处合并信息;本题合并左右向下路径得到直径,该题额外验证两侧高度差。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!