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

相关推荐
zhurui_xiaozhuzaizai22 分钟前
多向量检索:lanchain,dashvector,milvus,vestorsearch,MUVERA
人工智能·算法·机器学习·全文检索·milvus
大阳12333 分钟前
数据结构(概念及链表)
c语言·开发语言·数据结构·经验分享·笔记·算法·链表
2501_924731991 小时前
驾驶场景玩手机识别:陌讯行为特征融合算法误检率↓76% 实战解析
开发语言·人工智能·算法·目标检测·智能手机
爱编程的鱼2 小时前
计算机(电脑)是什么?零基础硬件软件详解
java·开发语言·算法·c#·电脑·集合
洛生&2 小时前
【abc417】E - A Path in A Dictionary
算法
亮亮爱刷题2 小时前
算法提升之数学(快速幂+逆元求法)
算法
1白天的黑夜13 小时前
前缀和-1314.矩阵区域和-力扣(LeetCode)
c++·leetcode·前缀和
weisian1513 小时前
力扣经典算法篇-42-矩阵置零(辅助数组标记法,使用两个标记变量)
算法·leetcode·矩阵
恣艺3 小时前
LeetCode 123:买卖股票的最佳时机 III
算法·leetcode·职场和发展