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

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

相关推荐
feifeigo1232 分钟前
matlab中随机森林算法的实现
算法·随机森林·matlab
躲着人群37 分钟前
次短路&&P2865 [USACO06NOV] Roadblocks G题解
c语言·数据结构·c++·算法·dijkstra·次短路
心动啊1212 小时前
支持向量机
算法·机器学习·支持向量机
小欣加油2 小时前
leetcode 1493 删掉一个元素以后全为1的最长子数组
c++·算法·leetcode
蓝风破云3 小时前
C++实现常见的排序算法
数据结构·c++·算法·排序算法·visual studio
艾醒3 小时前
大模型面试题剖析:Pre-Norm与Post-Norm的对比及当代大模型选择Pre-Norm的原因
算法
怀旧,4 小时前
【C++】 9. vector
java·c++·算法
浩浩测试一下5 小时前
06高级语言逻辑结构到汇编语言之逻辑结构转换 for (...; ...; ...)
汇编·数据结构·算法·安全·web安全·网络安全·安全架构
辞--忧5 小时前
K-Means 聚类算法详解与实战指南
算法·kmeans·聚类
尤超宇5 小时前
K 均值聚类(K-Means)演示,通过生成笑脸和爱心两种形状的模拟数据,展示了无监督学习中聚类算法的效果。以下是详细讲解:
算法·均值算法·聚类