【力扣100题】40.二叉树中的最大路径和

题目描述

二叉树中的路径被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中至多出现一次。该路径至少包含一个节点,且不一定经过根节点。

路径和是路径中各节点值的总和。

给你一个二叉树的根节点 root,返回其最大路径和。

示例 1:

复制代码
输入:root = [1,2,3]
输出:6
解释:最优路径是 2 -> 1 -> 3 ,路径和为 2 + 1 + 3 = 6

树结构:
    1
   / \
  2   3

路径 2→1→3:2+1+3=6

示例 2:

复制代码
输入:root = [-10,9,20,null,null,15,7]
输出:42
解释:最优路径是 15 -> 20 -> 7 ,路径和为 15 + 20 + 7 = 42

树结构:
      -10
       /
      9
       \
       20
      /  \
     15   7

路径 15→20→7:15+20+7=42

提示:

  • 树中节点数目范围是 [1, 3 * 10^4]
  • -1000 <= Node.val <= 1000

解题思路总览

方法 思路 时间复杂度 空间复杂度 适用场景
后序遍历 + 最大贡献值 计算每个节点能为父路径提供的最大贡献,O(1) 更新全局最大 O(n) O(h) 面试首选

核心原理:

  • 最大路径和 = 节点值 + max(0, 左子树最大贡献) + max(0, 右子树最大贡献)
  • 最大贡献值 = max(0, 节点值 + max(左贡献, 右贡献)),只能选一条边往上走

后序遍历 + 最大贡献值(推荐)

思路

这是一道经典的后序遍历变形题,核心思想:

  1. 最大路径和:路径可以从任意节点开始,到任意节点结束。但经过某个节点的最大路径和 = 节点值 + 左子树最大贡献 + 右子树最大贡献(左右贡献取正的)

  2. 最大贡献值:对于父节点来说,子节点只能"贡献"一条路径上来的值。所以一个节点能贡献给父节点的最大值 = max(0, 节点值 + max(左贡献, 右贡献)),因为贡献必须往上走,不能分叉

  3. 全局记录 :用 ans 记录所有节点作为"拐点"时的最大路径和

完整代码

cpp 复制代码
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int ans = INT_MIN;  // 全局最大路径和

    int dfs(TreeNode* node) {
        if (!node) return 0;  // 空节点贡献为 0

        // 递归计算左右子树的贡献值(如果为负则取 0,表示不往下走)
        int l = dfs(node->left);
        int r = dfs(node->right);

        // 更新全局最大路径和:
        // 路径可以是:左子树 → node → 右子树,或者只有其中一边,或者只有 node 自己
        ans = max(ans, l + r + node->val);

        // 返回该节点能为父路径贡献的最大值(只能选一条路径往上走)
        // 如果贡献是负数,则不贡献(取 0)
        return max(0, max(l, r) + node->val);
    }

    int maxPathSum(TreeNode* root) {
        dfs(root);
        return ans;
    }
};

算法流程图

以示例 2 为例,root = [-10,9,20,null,null,15,7]

复制代码
树结构:
       -10
        /
       9
        \
        20
       /  \
      15   7

后序遍历过程:

Step 1: dfs(15)
  l = dfs(null) = 0
  r = dfs(null) = 0
  ans = max(-inf, 0+0+15) = 15
  return max(0, max(0,0)+15) = 15

Step 2: dfs(7)
  l = dfs(null) = 0
  r = dfs(null) = 0
  ans = max(15, 0+0+7) = 15
  return max(0, max(0,0)+7) = 7

Step 3: dfs(20)
  l = dfs(15) = 15
  r = dfs(7) = 7
  ans = max(15, 15+7+20) = max(15, 42) = 42
  return max(0, max(15,7)+(-10)) = max(0, 15-10) = 5

Step 4: dfs(9)
  l = dfs(null) = 0
  r = dfs(20) = 5
  ans = max(42, 0+5+9) = max(42, 14) = 42
  return max(0, max(0,5)+9) = 14

Step 5: dfs(-10)
  l = dfs(9) = 14
  r = dfs(null) = 0
  ans = max(42, 14+0+(-10)) = max(42, 4) = 42
  return max(0, max(14,0)+(-10)) = max(0, 4) = 4

最终 ans = 42

逐行解析

cpp 复制代码
int ans = INT_MIN;  // 全局最大路径和
  • 初始化为 INT_MIN,因为节点值可能都是负数。
  • ans 记录遍历过程中遇到的最大路径和。
cpp 复制代码
int dfs(TreeNode* node) {
    if (!node) return 0;  // 空节点贡献为 0
  • 递归终止条件:空节点返回 0,表示没有贡献。
cpp 复制代码
    int l = dfs(node->left);
    int r = dfs(node->right);
  • 递归计算左子树和右子树的最大贡献值。
  • 这里用的是后序遍历(左右根),确保先处理子树,再处理根。
cpp 复制代码
    ans = max(ans, l + r + node->val);
  • 更新全局最大路径和
  • l + r + node->val 表示经过当前节点的路径和(左边贡献 + 根 + 右边贡献)
  • 这种路径的特点是:拐点在这里,可以同时走左右两边
  • 注意:lr 可能是负数,但在计算路径和时,负数会拉低总和,所以不会影响最大值
cpp 复制代码
    return max(0, max(l, r) + node->val);
  • 返回该节点对父路径的最大贡献
  • 只能选一条路径(左边或右边),所以取 max(l, r)
  • 加上当前节点值 node->val
  • 如果结果为负数,返回 0 表示不贡献(路径没必要往上走)

复杂度分析

复杂度 说明
时间 O(n) 每个节点访问一次
空间 O(h) 递归栈深度,h 为树高

优点: 一次遍历搞定,时间效率高
缺点: 需要理解"贡献值"的概念


关键概念区分

概念 定义 用途
最大路径和 经过树中任意节点的最大路径的和 最终答案
最大贡献值 一个节点能为父路径贡献的最大值(只能走一条边往上) 递归返回值

为什么贡献值只能选一条边?

因为父路径只能从一个方向上来,不能同时从两边拐上来再往上走。所以一个节点对父节点的贡献,只能选择左子树或右子树中贡献较大的那个。


面试追问 FAQ

问题 解答
Q1:为什么 l + r + node->val 能代表经过当前节点的最大路径和? 因为后序遍历保证了左右子树的贡献值 lr 都已经是各自子树中能贡献给父节点的最大值。当路径"经过"当前节点时,可以同时吸收左子树和右子树的贡献(因为路径可以在当前节点拐弯)。所以 l + r + node->val 就是以当前节点为拐点的最大路径和。
Q2:为什么要用 max(0, ...) 限制贡献值? 因为路径可以"不走"某个子树。如果某个子树对路径的贡献是负数,那还不如不选这条路径,直接把贡献值设为 0 即可。这确保了贡献值永远是非负的,不会拉低路径和。
Q3:为什么全局 ans 初始值是 INT_MIN 因为节点值可能是负数。如果树中只有一个节点且值为负,ans 应该就是这个负数。所以需要用一个很小的初始值来确保正确答案能被正确更新。
Q4:为什么返回值是 max(l, r) + node->val 而不是 l + r + node->val 因为返回值表示的是"该节点对父节点的贡献"。父路径只能从一边上来(从上往下走),不能同时从两边拐。所以只能选 max(l, r) 而不是 l + r
Q5:为什么返回值要取 max(0, ...) 如果 max(l, r) + node->val 是负数,说明当前节点对任何路径都是"拖后腿"的,父路径不应该包含这个节点。所以返回 0 表示"不贡献",相当于路径在这里结束或根本不走这条分支。
Q6:这道题和 437 题(路径总和 III)有什么区别? 437 题要求路径必须向下(父子关系),但可以有多个起点和终点。本题更通用,路径可以是任意形状(可以拐弯)。437 题用前缀和 + HashMap 解决;本题用后序遍历 + 贡献值解决。

相关题目

题目 难度 关键点
124. 二叉树中的最大路径和 困难 后序遍历,贡献值
437. 路径总和 III 中等 前缀和,HashMap
112. 路径总和 简单 从根到叶子的路径和
129. 求根到叶子节点数字之和 中等 路径遍历变形

总结

要点 说明
核心原理 后序遍历计算每个节点的贡献值,O(1) 更新全局最大路径和
最大路径和 ans = max(ans, l + r + node->val),经过当前节点的路径和
最大贡献值 return max(0, max(l, r) + node->val),只能选一条边往上走
为什么要 max(0,...) 贡献值不能为负,否则会拉低路径和,直接取 0 即可
时间复杂度 O(n),每个节点访问一次
空间复杂度 O(h),递归栈深度

相关推荐
洛水水1 小时前
【力扣100题】37.从前序与中序遍历序列构造二叉树
c++·算法·leetcode
zyq99101_11 小时前
递归与动态规划实战代码解析
python·算法·蓝桥杯
橘白3162 小时前
rl笔记(一):策略梯度更新算法推导
人工智能·算法·机器人·强化学习
hhhhhaaa2 小时前
多节点矩阵式任务系统:统一配置中心与动态规则引擎架构设计
后端·算法·架构
吃着火锅x唱着歌2 小时前
LeetCode 739.每日温度
算法·leetcode·职场和发展
如竟没有火炬2 小时前
去除重复字母——贪心+单调栈
开发语言·数据结构·python·算法·leetcode·深度优先
薛定e的猫咪2 小时前
【ICML 2025】MODULI:基于扩散模型解锁离线多目标强化学习的偏好泛化
人工智能·学习·算法·机器学习
Brilliantwxx2 小时前
【C++】priority_queue以及 仿函数 的学习
开发语言·c++·笔记·学习·算法
风味蘑菇干2 小时前
斗地主案例
java·数据结构·算法