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

相关推荐
mCell8 小时前
Lua 编程入门:从基础语法到元表
javascript·算法·lua
wuyk55510 小时前
98.C语言易混难点:字符数组与字符串指针的底层差异
c语言·开发语言·c++·stm32·嵌入式硬件·算法
峥无10 小时前
从0到1手撕红黑树:封装实现 my_map 与 my_set(SGI-STL 源码级深度解析)
开发语言·c++·笔记·算法·stl
不会代码的小猴11 小时前
3. 控件学习1
开发语言·c++·笔记·qt·算法
碳基猿11 小时前
案例|普通内容创作者如何利用 CreBee 打造个人内容创业系统:从一个账号到多平台内容资产矩阵
职场和发展·新媒体运营·创业创新·程序员创富·新媒体矩阵
203号居民13 小时前
LeetCode hot 100 —41. 缺失的第一个正数
数据结构·算法·leetcode
专注仿真15 小时前
问答大模型技术方案算法实现-熵权法融合算法 + 交叉编码器重排算法
人工智能·python·算法·语言模型·问答大模型关键算法·熵权融合算法·交叉编码器重排算法
evans在进步15 小时前
LeetCode 322:零钱兑换——Java 动态规划详解
java·leetcode·动态规划
地平线开发者16 小时前
量化为什么会有损失:从量化误差到精度下降
算法