二叉树的直径:别再暴力破解了,一次遍历才是正解!
摘要:LeetCode 543. 二叉树的直径,你还在傻傻地算每个节点的左右深度求和吗?时间复杂度太高啦!本文带你通过“后序遍历”的思维,在一次递归中同时求出深度和直径,用最优解法征服面试官!
前言
哈喽大家好,我是爱摸鱼的打工仔。
今天咱们来聊聊 LeetCode 上的一道经典题目——543. 二叉树的直径。
这题乍一看挺简单,不就是求两个节点之间的最长路径吗?很多小伙伴的第一反应是:“那我遍历每个节点,算出它的左子树深度和右子树深度,加起来不就行了?”
没错,思路是对的,但如果你真的这么写,不仅代码啰嗦,而且时间复杂度会飙到 ,在数据量大的时候直接超时。
今天,我就带大家用 “后序遍历” 的思维,在一次递归中把活干完,让你的代码既优雅又高效!
核心知识点:深度 vs 直径
在动手写代码之前,我们必须先搞清楚两个概念,这直接决定了你的解题思路:
-
二叉树的深度(Depth) :
- 指的是从某个节点出发,向下能走的最长路径的边数。
- 计算方式:
1 + max(左子树深度, 右子树深度)。 - 特点:只能走一边(要么走左边,要么走右边),因为深度是通向根节点的路径。
-
二叉树的直径(Diameter) :
- 指的是树中任意两个节点之间最长路径的边数。
- 关键点:这条路径可能经过根节点,也可能不经过。
- 计算方式:对于任意一个节点,经过它的最长路径 =
左子树深度 + 右子树深度。
一句话总结:
求深度是“一条路走到黑”(取最大值),求直径是“左右开弓”(两边相加)。
解题思路:后序遍历(一次遍历法)
我们不需要对每个节点都单独调用一次求深度的函数(那样太慢了!)。我们可以在求深度的过程中,顺便把直径给算出来。
核心逻辑:
- 我们定义一个递归函数
dfs(node),它的职责是返回以node为根节点的树的深度。 - 在递归的过程中(后序遍历位置),我们已经知道了
node.left的深度和node.right的深度。 - 此时,我们可以计算经过当前节点
node的直径:left_depth + right_depth。 - 拿这个值和全局最大直径
max_d比较,如果更大,就更新它。 - 最后,按照深度的定义,向上返回
1 + max(left_depth, right_depth)。
代码详解
下面是具体的代码实现,我加上了非常详细的注释,咱们一行行来看:
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int:
# 初始化最大直径为 0
# 这里用 self.max_d 是为了在递归函数内部也能方便地修改它
self.max_d = 0
# 定义递归函数:计算以 node 为根的子树的深度
def dfs(node: Optional[TreeNode]) -> int:
# 1. 递归终止条件:如果是空节点,深度为 0
if not node:
return 0
# 2. 递归计算左子树的深度
# 注意:这里不仅仅是计算,还会顺带更新左子树内部可能存在的最大直径
left_depth = dfs(node.left)
# 3. 递归计算右子树的深度
right_depth = dfs(node.right)
# 4. 【核心步骤】更新最大直径
# 经过当前节点的最长路径 = 左子树深度 + 右子树深度
# 我们拿它和目前的全局最大值比一比,取大的那个
current_diameter = left_depth + right_depth
self.max_d = max(self.max_d, current_diameter)
# 5. 返回当前节点的深度
# 深度定义:1 (当前节点到父节点的边) + 左右子树深度的最大值
# 为什么要返回深度?因为父节点需要用到它来计算父节点的直径!
return 1 + max(left_depth, right_depth)
# 启动递归,从根节点开始
dfs(root)
# 递归结束后,self.max_d 里存的就是整棵树的最大直径
return self.max_d
图解原理(脑补一下)
想象一棵树:
1
/ \
2 3
/ \
4 5
-
递归到底层,节点
4和5返回深度1。 -
回到节点
2:- 左边深度是 1(节点4),右边深度是 1(节点5)。
- 经过节点
2的直径 =1 + 1 = 2。更新max_d = 2。 - 节点
2返回自己的深度1 + max(1, 1) = 2。
-
回到节点
3:- 它是叶子节点,左右深度都是 0。直径 0。
- 节点
3返回深度1。
-
回到根节点
1:- 左边深度是 2(来自节点2),右边深度是 1(来自节点3)。
- 经过节点
1的直径 =2 + 1 = 3。更新max_d = 3。 - 返回深度
1 + max(2, 1) = 3。
最终结果就是 3。
复杂度分析
-
- 我们只需要遍历每个节点一次,在遍历的同时完成了深度的计算和直径的更新。比暴力法快得多!
-
- 取决于递归栈的深度,也就是树的高度。最坏情况下(链状树)是 ,平均情况是 。
总结
做二叉树的题目,一定要分清**“自顶向下”和“自底向上”**。
- 这道题如果自顶向下(对每个节点求深度),就是暴力解法。
- 利用自底向上(后序遍历) ,在返回深度的时候顺便“收割”直径信息,才是最优解。
希望这篇笔记能帮你彻底搞懂二叉树直径!如果觉得有用,记得给爱摸鱼的打工仔点个赞哦,我们下期见!