因为路径可跨根但上传只能单侧,故需分离全局最大路径和与单侧最大收益;负子树贡献截为0以避免拉低总和,空节点返回0,全局初值须为INT_MIN。

为什么 maxPathSum 不能直接返回子树最大路径和?
因为路径在二叉树中可以「跨过根节点」,但递归时每个节点只能向上贡献一条连续路径(即从该节点出发,向下走且不回头)。所以必须区分两个值:当前节点作为路径最高点时的全局最大和(可左右子树都参与),与当前节点能向上提供的单侧最大收益(只能选左或右,且必须包含自己)。
常见错误是让递归函数只返回一个值,导致无法同时捕获「跨根路径」和「上传路径」。正确做法是用全局变量或引用参数记录历史最大值,递归函数本身只返回单侧最大收益。
-
maxPathSum函数负责初始化并调用辅助递归函数 - 辅助函数(如
gainFromSubtree)返回以当前节点为起点、向下延伸的最大单向路径和(至少包含自己) - 每次进入辅助函数,先算左右子树的
gainFromSubtree值,再计算「经过当前节点的最大路径和」:node->val + left_gain + right_gain - 这个「经过当前节点」的值要实时更新全局最大值,但它不能作为返回值——因为父节点无法拼接两条向下路径
gainFromSubtree 中负收益子树为什么要截断为 0?
因为路径必须连续,但可以选择「不走某条子路径」。如果左子树最大单向收益是 -5,加上它只会拉低总和,此时最优策略是只取当前节点自己(即把左路径“截掉”,视作收益 0)。
注意:这里不是把节点值设为 0,而是把子树贡献设为 0。例如节点值为 2,左子树 gainFromSubtree 返回 -3,右子树返回 4,则当前节点能上传的最大收益是 2 + max(0, -3) + max(0, 4) → 实际上传的是 2 + 0 + 4 = 6,而「经过当前节点」的完整路径和是 2 + 0 + 4 = 6(左被截断),但如果右也是负的,比如右返回 -1,则完整路径和就是 2 + 0 + 0 = 2,即只含自己。
- 截断逻辑写成
max(0, left_gain),不是max(0, left_gain + node->val) - 节点值
node->val必须无条件加入上传收益(否则路径就断了) - 空节点返回 0,天然支持截断:叶子节点的左右都是 0,上传值就是自身值
边界情况怎么处理:空节点、单节点、全负数?
空节点返回 0 是安全的,既符合「无贡献」语义,又避免指针解引用;单节点树的路径和就是该节点值;全负数时,最大路径和必然是最大的那个负数(不能为 0,因为路径不能为空)。
关键点在于全局最大值的初始值不能设为 0。例如输入 [-2,-3],若初值为 0,最终会错判为 0。必须初始化为 INT_MIN 或直接用第一个访问到的节点值。
- 递归前初始化
max_sum = INT_MIN,每次计算node->val + left_gain + right_gain后更新它 - 空节点在辅助函数中直接 return 0,不参与
max_sum更新 - 即使所有节点为负,
node->val + 0 + 0就是节点自身值,会被捕获到 - 不要在辅助函数里对
node->val做额外 clamp,否则会破坏单节点场景
C++ 实现里容易忽略的 const 和 nullptr 安全
LeetCode 的 TreeNode 定义中 left 和 right 是 TreeNode*,不是智能指针,所以必须显式判空。另外,递归函数参数建议加 const TreeNode* root,避免误改结构;全局最大值用引用传入比用 static 或成员变量更清晰、线程友好。
性能上无显著差异,但少一次解引用、多一层编译期检查,值得做。
- 入口函数中先判
if (!root) return 0;,但辅助函数内部仍需对root->left和root->right分别判空 - 辅助函数签名推荐:
int gainFromSubtree(const TreeNode* root, int& max_sum) - 不要写
if (root == nullptr)以外的空指针假设,C++ 不保证未定义行为一定 crash - 返回值类型必须是
int,不能是long long,除非题目明确说可能溢出(本题约束值域在 int 内)
实际写的时候,最常卡住的地方不是逻辑,而是忘记更新 max_sum 的时机——它必须在计算完 node->val + left_gain + right_gain 后立刻更新,而不是等到递归返回。这点一旦漏掉,全负数用例必然失败。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











