LeetCode 124:二叉树中的最大路径和

问题本质与核心挑战
给定二叉树,求任意路径 (节点相连,无分叉,每个节点最多出现一次)的节点值和的最大值。核心挑战:
- 路径可从任意节点开始/结束,需覆盖所有可能的路径;
- 需高效处理子树贡献 与全局最大值的动态更新,避免重复计算。
核心思路:递归 + 贡献值计算
1. 关键观察
- 路径的两种形态 :
- 单节点路径(仅当前节点);
- 跨子树路径(当前节点 + 左子树路径 + 右子树路径);
- 单侧路径(当前节点 + 左子树路径 或 当前节点 + 右子树路径,可延伸至父节点)。
- 贡献值定义 :当前节点能为父节点所在路径提供的最大和(只能选左或右子树的单侧路径,避免分叉)。
2. 算法设计
- 全局变量
maxSum
:记录遍历过程中发现的最大路径和(初始为Integer.MIN_VALUE
,处理全负数情况)。 - 递归函数
dfs(node)
:- 返回 当前节点对父节点的最大贡献值 (单侧路径和,即
node.val + max(左子树贡献, 右子树贡献)
)。 - 过程中计算 当前节点作为根的跨子树路径和 (
node.val + 左子树贡献 + 右子树贡献
),并更新maxSum
。
- 返回 当前节点对父节点的最大贡献值 (单侧路径和,即
算法步骤详解(以示例 2 为例:root = [-10,9,20,null,null,15,7]
)
步骤 1:初始化全局变量
java
int maxSum = Integer.MIN_VALUE; // 覆盖全负数场景(如节点值为-3)
步骤 2:递归遍历(DFS)
定义递归函数 dfs(TreeNode node)
,返回当前节点对父节点的单侧贡献值:
java
private int dfs(TreeNode node) {
if (node == null) return 0; // 空节点贡献0(无值)
// 1. 计算左右子树的贡献(负数则舍弃,因为加负数会减小总和)
int leftGain = Math.max(dfs(node.left), 0);
int rightGain = Math.max(dfs(node.right), 0);
// 2. 计算当前节点作为根的跨子树路径和(左+当前+右)
int currentSum = node.val + leftGain + rightGain;
maxSum = Math.max(maxSum, currentSum); // 更新全局最大值
// 3. 返回当前节点对父节点的单侧贡献(只能选左或右,避免分叉)
return node.val + Math.max(leftGain, rightGain);
}
步骤 3:递归过程演示(示例 2 分解)
递归层级 | 节点 | 左子树贡献 | 右子树贡献 | 当前跨子树和 | 单侧贡献值 | maxSum 变化 |
---|---|---|---|---|---|---|
叶子层 | 15 | 0(空) | 0(空) | 15+0+0=15 | 15+0=15 | 15 |
叶子层 | 7 | 0(空) | 0(空) | 7+0+0=7 | 7+0=7 | 15(仍为15) |
父层 | 20 | 15(左15的贡献) | 7(右7的贡献) | 20+15+7=42 | 20+15=35 | 42 |
父层 | 9 | 0(空) | 0(空) | 9+0+0=9 | 9+0=9 | 42(仍为42) |
根层 | -10 | 9(左9的贡献) | 35(右20的贡献) | -10+9+35=34 | -10+35=25 | 42(仍为42) |
步骤 4:结果返回
遍历结束后,maxSum
即为全局最大路径和:
java
public int maxPathSum(TreeNode root) {
dfs(root);
return maxSum;
}
完整代码(Java)
java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
private int maxSum = Integer.MIN_VALUE; // 初始化为极小值,处理全负数情况
public int maxPathSum(TreeNode root) {
dfs(root);
return maxSum;
}
private int dfs(TreeNode node) {
if (node == null) {
return 0; // 空节点贡献0
}
// 递归计算左右子树的贡献,负数则舍弃(取0)
int leftGain = Math.max(dfs(node.left), 0);
int rightGain = Math.max(dfs(node.right), 0);
// 计算当前节点作为根的跨子树路径和(左+当前+右)
int currentSum = node.val + leftGain + rightGain;
// 更新全局最大路径和
maxSum = Math.max(maxSum, currentSum);
// 返回当前节点对父节点的单侧贡献(只能选左或右,避免分叉)
return node.val + Math.max(leftGain, rightGain);
}
}
关键逻辑解析
1. 为什么舍弃负数贡献?
若子树的贡献为负数(如左子树和为 -5
),加上它会减小当前节点的路径和 ,因此选择舍弃(取 0
),仅保留当前节点自身的值。
2. 单侧贡献 vs 跨子树和
- 单侧贡献:供父节点延伸路径(如父节点 → 当前节点 → 左子树),只能选左或右(避免分叉)。
- 跨子树和:当前节点为根的完整路径(左 → 当前 → 右),无法延伸至父节点,需立即更新全局最大值。
3. 全负数场景处理
初始 maxSum = Integer.MIN_VALUE
,确保即使所有节点为负数(如 root = [-3]
),也能正确捕获 currentSum = -3
。
复杂度分析
- 时间复杂度 :
O(n)
(每个节点遍历一次,递归深度为树高)。 - 空间复杂度 :
O(h)
(递归栈空间,h
为树高;最坏h = n
,如链表;最优h = logn
,如平衡树)。
该方法通过 递归分解子问题 ,巧妙区分"单侧贡献"和"跨子树和",在 O(n)
时间内高效求解,是二叉树路径问题的经典模板。核心思想可推广到"最大路径和""最长路径"等类似问题。