543. 二叉树的直径(Diameter of Binary Tree)

难度:简单 | 主题:二叉树、DFS、后序遍历

题目

给定一棵二叉树,计算它的直径长度。二叉树的直径是任意两个节点路径长度的最大值(边数)。这条路径可能穿过也可能不穿过根节点。

示例

 
输入:root = [1,2,3,4,5]
 
输出:3
 
解释:路径 4→2→1→3 或 5→2→1→3,长度 3
 

思路

先讲个故事:城市间的公路

想象你在规划一条横跨多个城市的公路,公路只能沿树形结构修建(每个城市最多连到下一个城市)。你要找最长的直达公路——可以穿过任意城市(节点),但不能分叉。

你会发现:最长公路的最高点一定是某个城市,公路从这个城市向左延伸一段、向右延伸一段。公路长度 = 向左的段数 + 向右的段数。

所以问题转化为:对每个节点,计算左右子树的最大深度之和,取全局最大值。


引导式推导:从最大深度到直径

第 1 步:最大深度怎么算?

104二叉树的最大深度 告诉我们:

 
深度 = 1 + max(左子树深度, 右子树深度)
 

第 2 步:直径和深度的关系

对任意节点,经过它的最长路径 = 左子树深度 + 右子树深度(边数)。

为什么呢?因为最长路径的”拐点”一定在某个节点上,拐点向左走到最深叶子、向右走到最深叶子,中间经过拐点。

 
         ①             ← 拐点
 
        ↙ ↘
 
      ②    ③          左深度 2,右深度 1
 
     ↙ ↘
 
   ④    ⑤
 
   路径 ④→②→①→③:边数 = 左深度 2 + 右深度 1 = 3
 

第 3 步:不一定经过根节点

最大直径可能在任意子树内部。所以遍历每个节点时都要尝试更新,不能只看根节点。

第 4 步:后序 DFS 框架

经典套路:「后序 + 全局变量」——递归返回子树深度供父节点用,同时用全局变量记录当前拐点的直径。

和 124二叉树中的最大路径和 完全相同的框架,只不过 124 加的是节点值,这里加的是深度。


代码

 
def diameterOfBinaryTree(self, root):
 
    self.ans = 1                     # 全局最大节点数(直径边数 = 节点数 - 1)
 
    def depth(node):
 
        if not node:
 
            return 0
 
        L = depth(node.left)         # 后序:左子树深度
 
        R = depth(node.right)        # 后序:右子树深度
 
        self.ans = max(self.ans, L + R + 1)  # 拐点处更新:路径节点数 = 左深 + 右深 + 1
 
        return max(L, R) + 1         # 返回当前子树高度(供上层继续算深度)
 
    depth(root)
 
    return self.ans - 1              # 边数 = 节点数 - 1
 

关键理解「返回值 vs 全局更新」:

  • depth() 返回子树高度(单边最长),因为父节点只需要知道”你这边最深多少”才能算自己的高度

  • self.ans 记录的是路径节点数(两边之和 + 1),因为直径需要两边相加

如果只用返回值不用全局变量,你无法同时”向上报深度”和”向左右两边求和”——这是后序 DFS 的精髓。


复杂度

指标解释
时间O(n)每个节点访问一次
空间O(h)递归栈深度,最坏 O(n)(链),平均 O(log n)

实战考量

频率分析

出现在:美团/字节/阿里 二叉树后序遍历题中频。30% 的候选人会犯同一个错——只考虑经过根节点的路径。

延伸思考

Q:直径一定要经过根节点吗?

A:不一定。直径的定义是”任意两个节点的最长路径”,如果根节点的一棵子树内部就有很长的路径,完全可能不经过根。所以在每个节点(拐点)都要更新。

Q:为什么 depth 返回的是高度,但更新直径用的是 left + right?

A:因为高度 = 从节点到最远叶子向下的边数。直径 = 经过该节点的最长路径 = 向左走到最深 + 向右走到最深 = left + right。这是两个不同的量:高度是单边的,直径是双边的

Q:如果要求返回具体的路径节点,不只是长度?

A:需要记录直径两端的叶子节点,然后从根做 DFS 回溯找到它们之间的路径。实践中一般只问长度。

Q:如果树里有负权边呢?

A:那就是 124二叉树中的最大路径和 的思路,但路径和允许”不走两边”(可以只走一边或单节点)。直径是边数计数,不涉及负值。

易错点

  • 初始值 ans=1(节点数),不是 0——单节点树节点数为 1,减 1 得 0 边

  • 更新直径用 L + R(边数)或 L + R + 1(节点数),返回值用 max(L, R) + 1

  • 和 124 的区别:124 路径和可以只取一边(负值不取),直径必须两边都取(因为是边数计数)


生活类比

找直径 → 找最长公路

想象一个树形城市群,你要找最远两个城市的路程。

在每个城市(节点),记录”向左最远能走多远”和”向右最远能走多远”。

经过这个城市的最长路 = 向左 + 向右。

全局最长路 = 所有城市里这个和的最大值。

把城市比作交通枢纽:每个枢纽只能记住自己单向最远能到哪(返回值),

但全球交通局记录每个枢纽左右最远之和(全局变量)。


相关题目

题目关系
104二叉树的最大深度直径的基础——先会算深度
124二叉树中的最大路径和完全相同的后序 DFS 框架,值替换深度
110平衡二叉树同样是后序 + 高度比较

→ 返回题单:LeetCode学习路线图 > 五、二叉树

速记卡(面试闪卡)

Q1:一句话讲清「543. 二叉树的直径(Diameter of Binary Tree)」到底是什么?

A:求二叉树任意两节点间最长路径的边数,用后序 DFS 在每个拐点算左右深度和。

Q2:题目:城市间最长公路(diameter of tree) —— 怎么理解?

A:像在树形城市群规划最长直达公路——只能沿树走、不能分叉,可穿过任意城市。答案就是某节点处”向左最远 + 向右最远”的边数,不一定过根。

Q3:思路:后序 DFS + 全局变量(post-order DFS) —— 怎么理解?

A:递归返回单边深度供父节点用,同时用全局变量 ans 记录每个拐点的 左深+右深。像交通枢纽只记单向最远,交通局记左右之和取最大。深度单边、直径双边,别混。

Q4:代码:返回值 vs 全局更新(depth return) —— 怎么理解?

A:depth() 返回 max(L,R)+1(高度),self.ansL+R+1(节点数)。ans 初值 1(单节点),最后返回 ans-1 得边数。复用 124 最大路径和框架,只是把值换成深度。

Q5:复杂度与实战(O(n) time, O(h) space) —— 怎么理解?

A:时间 O(n)(每节点一次),空间 O(h)(递归栈,最坏链 O(n))。美团字节阿里中频,30% 人错在”只考虑过根的路径”——必须每节点更新。

Q6:核心速记主线有哪些?

  • 题目:任意两节点最长路径边数

  • 思路:后序 DFS,每拐点算左深+右深(post-order)

  • 代码:返回值记高度,全局 ans 记 L+R+1

  • 复杂度:时间 O(n)、空间 O(h)

  • 实战:别只看过根路径,每节点更新

口诀

A:公路穿城任意连,

左右深度相加算;

每点都作拐点看,

最大直径现眼前。

相关链接