【力扣-543.二叉树的直径】Python笔记

0 阅读5分钟

二叉树的直径:别再暴力破解了,一次遍历才是正解!

摘要:LeetCode 543. 二叉树的直径,你还在傻傻地算每个节点的左右深度求和吗?时间复杂度太高啦!本文带你通过“后序遍历”的思维,在一次递归中同时求出深度和直径,用最优解法征服面试官!


前言

哈喽大家好,我是爱摸鱼的打工仔

今天咱们来聊聊 LeetCode 上的一道经典题目——543. 二叉树的直径

这题乍一看挺简单,不就是求两个节点之间的最长路径吗?很多小伙伴的第一反应是:“那我遍历每个节点,算出它的左子树深度和右子树深度,加起来不就行了?”

没错,思路是对的,但如果你真的这么写,不仅代码啰嗦,而且时间复杂度会飙到 O(N2)O(N^2),在数据量大的时候直接超时。

今天,我就带大家用 “后序遍历” 的思维,在一次递归中把活干完,让你的代码既优雅又高效!


核心知识点:深度 vs 直径

在动手写代码之前,我们必须先搞清楚两个概念,这直接决定了你的解题思路:

  1. 二叉树的深度(Depth)

    • 指的是从某个节点出发,向下能走的最长路径的边数。
    • 计算方式1 + max(左子树深度, 右子树深度)
    • 特点:只能走一边(要么走左边,要么走右边),因为深度是通向根节点的路径。
  2. 二叉树的直径(Diameter)

    • 指的是树中任意两个节点之间最长路径的边数。
    • 关键点:这条路径可能经过根节点,也可能不经过
    • 计算方式:对于任意一个节点,经过它的最长路径 = 左子树深度 + 右子树深度

一句话总结
求深度是“一条路走到黑”(取最大值),求直径是“左右开弓”(两边相加)。


解题思路:后序遍历(一次遍历法)

我们不需要对每个节点都单独调用一次求深度的函数(那样太慢了!)。我们可以在求深度的过程中,顺便把直径给算出来。

核心逻辑:

  1. 我们定义一个递归函数 dfs(node),它的职责是返回以 node 为根节点的树的深度
  2. 在递归的过程中(后序遍历位置),我们已经知道了 node.left 的深度和 node.right 的深度。
  3. 此时,我们可以计算经过当前节点 node 的直径left_depth + right_depth
  4. 拿这个值和全局最大直径 max_d 比较,如果更大,就更新它。
  5. 最后,按照深度的定义,向上返回 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
  1. 递归到底层,节点 45 返回深度 1

  2. 回到节点 2

    • 左边深度是 1(节点4),右边深度是 1(节点5)。
    • 经过节点 2 的直径 = 1 + 1 = 2。更新 max_d = 2
    • 节点 2 返回自己的深度 1 + max(1, 1) = 2
  3. 回到节点 3

    • 它是叶子节点,左右深度都是 0。直径 0。
    • 节点 3 返回深度 1
  4. 回到根节点 1

    • 左边深度是 2(来自节点2),右边深度是 1(来自节点3)。
    • 经过节点 1 的直径 = 2 + 1 = 3。更新 max_d = 3
    • 返回深度 1 + max(2, 1) = 3

最终结果就是 3


复杂度分析


    • 我们只需要遍历每个节点一次,在遍历的同时完成了深度的计算和直径的更新。比暴力法快得多!

    • 取决于递归栈的深度,也就是树的高度。最坏情况下(链状树)是 O(N)O(N),平均情况是 O(logN)O(\log N)

总结

做二叉树的题目,一定要分清**“自顶向下”“自底向上”**。

  • 这道题如果自顶向下(对每个节点求深度),就是暴力解法。
  • 利用自底向上(后序遍历) ,在返回深度的时候顺便“收割”直径信息,才是最优解。

希望这篇笔记能帮你彻底搞懂二叉树直径!如果觉得有用,记得给爱摸鱼的打工仔点个赞哦,我们下期见!