← 返回题目列表

如何求二叉树的直径和最大路径和?

高频 中等 第 16 / 30 题 更新于 2026/07/29
二叉树直径最大路径和树形DP

简化版

这两题是同一个套路——「后序递归 + 用全局变量记录经过某节点的最优解」。递归函数返回「从当前节点往下能延伸的最优单边值」(直径返回单边最大深度、路径和返回单边最大和),同时在每个节点处计算「左 + 当前 + 右」更新全局答案。区别只在于统计的是「边数/节点数」还是「节点值之和」。

详细版

二叉树的直径

直径 = 树中任意两节点间最长路径的边数(这条路径不一定过根)。经过某节点的最长路径 = 左子树最大深度 + 右子树最大深度。

int diameter = 0;
int diameterOfBinaryTree(TreeNode root) {
    depth(root);
    return diameter;
}
int depth(TreeNode node) {          // 返回:以 node 为根往下的最大深度(边数)
    if (node == null) return 0;
    int left  = depth(node.left);
    int right = depth(node.right);
    diameter = Math.max(diameter, left + right);  // 经过 node 的路径边数
    return Math.max(left, right) + 1;             // 往上只能选一条边延伸
}

二叉树的最大路径和

路径和 = 路径上节点值之和,路径可从任意节点到任意节点。经过某节点的最大路径和 = 该节点值 + 左侧最大贡献 + 右侧最大贡献(负贡献舍弃)。

int maxSum = Integer.MIN_VALUE;
int maxPathSum(TreeNode root) {
    gain(root);
    return maxSum;
}
int gain(TreeNode node) {           // 返回:从 node 往下单边能贡献的最大和
    if (node == null) return 0;
    int left  = Math.max(gain(node.left),  0);   // 负的就不要(取 0)
    int right = Math.max(gain(node.right), 0);
    maxSum = Math.max(maxSum, node.val + left + right);  // 经过 node 的路径和
    return node.val + Math.max(left, right);             // 往上只能带一条边
}

完整版教学

一、共同套路:树形 DP 的「拐点」思想

这两题都属于「树里找一条最优路径」。关键洞察是:任何一条路径,都有一个「最高点」(拐点)——路径在这个节点从左子树拐向右子树。 于是可以枚举每个节点作为拐点,计算「经过它、由左右两条向下的臂拼成」的路径值,取全局最大。

  • 遍历用后序(先拿到左右子树的信息,再算当前节点)。
  • 用一个全局变量记录「以每个节点为拐点」的最优解。
  • 递归返回值只能是「单边」的贡献(左或右选一条),因为返回给父节点后,父节点要把它当成一条向下的臂,路径不能有分叉。

二、为什么返回值是「单边」,全局更新是「两边」

这是本类题最容易混的点:

  • 更新全局答案时用 左 + 当前 + 右——因为以当前节点为拐点的路径可以同时用上左右两条臂。
  • 返回给父节点时只能用 当前 + max(左, 右)——父节点要把当前子树当作「一条向下延伸的直线」,如果带了左右两条臂就分叉了,没法再往上接。

一句话:「算答案可以拐弯,往上传只能走直线」。

三、直径与路径和的差异

直径最大路径和
统计对象边数(或节点数)节点值之和
单边返回max(左,右) + 1node.val + max(左,右)
全局更新左 + 右node.val + 左 + 右
负值处理不涉及负贡献舍弃(和 0 取大)

最大路径和多了一步:如果某侧子树的贡献是负的,就不要它(取 0),因为负数只会拖累总和。直径不需要这步(深度总是非负)。

四、易错点

  • 直径是边数left + right 就是经过该节点的路径边数,不用再 +1(若题目要节点数则 +1)。
  • 路径和的负值舍弃Math.max(gain(child), 0),漏了会在有负节点时算错。
  • 全局变量初始值:路径和至少包含一个节点,maxSum 初值要设成最小值(不能设 0,否则全负节点的树会错)。
  • 更新全局和返回值是两个不同的表达式,别写成一样。

五、这是一类题的模板

「二叉树中某种最优路径/最优子树」——直径、最大路径和、最长同值路径、打家劫舍 III 等,都是这个「后序递归 + 全局变量记录拐点最优 + 返回单边值」的模板。认出这个模式,一类题就都会了。

六、常见误区与追问

对比点递归返回值全局答案更新
二叉树直径max(leftDepth, rightDepth) + 1leftDepth + rightDepth
最大路径和node.val + max(leftGain, rightGain)node.val + leftGain + rightGain

记忆钩子:往上交差只能交一条链,自己结算答案时才可以把左右两条链拼起来。

  • 误区:递归返回值可以同时包含左右两边。 返回给父节点后还要继续接到父节点,路径不能分叉,所以只能返回单边贡献。
  • 误区:直径一定经过根节点。 最长路径可能完全在某个子树内部,因此要枚举每个节点作为拐点并维护全局最大值。
  • 误区:最大路径和的全局变量可以初始化为 0。 如果整棵树节点值全为负数,答案应该是最大的那个负数,初始化为 0 会错。
  • 误区:负贡献也应该保留。 最大路径和里负数分支会降低总和,向当前节点贡献时要和 0 取最大。
  • 追问:直径返回边数还是节点数? 常见定义返回边数,leftDepth + rightDepth 即可;若题目要节点数,再在结果口径上加 1。
  • 追问:为什么必须后序? 当前节点要依赖左右子树的深度或最大贡献,只有后序能先算子树再结算当前节点。

七、加强记忆

直径和最大路径和是同一套树形 DP模板:后序递归,用全局变量记录「以每个节点为拐点、左+当前+右」的最优解,而递归返回值只能带单边 当前 + max(左,右)(往上传不能分叉)。核心口诀:算答案可拐弯、往上传走直线。路径和额外要舍弃负贡献(和 0 取大),且 maxSum 初值设最小值。