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) 时间内高效求解,是二叉树路径问题的经典模板。核心思想可推广到"最大路径和""最长路径"等类似问题。