Vvvrf / DSA.log← 返回全部笔记
LEETCODE 543Easy

二叉树的直径

直径的答案藏在每个节点的「左深 + 右深」里,顺着求深度的递归就能顺手捞出来。

问题拆解

直径是任意两节点间最长路径的边数,这条路径可能穿过根,也可能不穿过。如果对每个节点都单独去搜一遍最长路径,复杂度会很高,而且大量深度会被重复计算。

关键洞察是换一个统计口径:任何一条路径,都有一个「最高点」——路径上离根最近的那个节点。以某个节点为最高点的最长路径,恰好是它左子树深度加右子树深度。于是直径就是所有节点这个值的最大者。

与其枚举路径,不如枚举路径的转折点:每个节点贡献一个「左深 + 右深」,取全局最大即可。

求深度时顺带更新

我们本来就要为每个节点算深度,那就在同一趟递归里,把「左深 + 右深」拿去刷新一个全局最大值:

def diameterOfBinaryTree(root):
    best = 0

    def depth(node):
        nonlocal best
        if not node:
            return 0
        left = depth(node.left)
        right = depth(node.right)
        best = max(best, left + right)
        return 1 + max(left, right)

    depth(root)
    return best

depth 的返回值仍然是老老实实的子树深度,供上层使用;而 best 只在递归内部被悄悄更新。返回值和副作用各司其职,是这类题的常见分工。注意直径按边数计,所以是 left + right 而不用再加一。

复杂度

指标 复杂度 原因
时间 O(n) 深度只在一趟后序遍历中算完
空间 O(h) 递归栈深度等于树高

可以迁移的模式

  • 求「路径最值」时,把路径按其最高点归类,往往能把二维搜索压成一维统计;
  • 递归函数可以「返回一个量给上层,同时用副作用维护另一个全局量」;
  • 遇到「最长路径」「最大和路径」这类树形问题,先想想每个节点作为转折点能贡献什么。

一次遍历同时算两件事,是把暴力枚举优化掉的常见手法。