题源:LeetCode
124. 二叉树中的最大路径和
路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
路径和 是路径中各节点值的总和。
给你一个二叉树的根节点 root ,返回其 最大路径和 。
示例 1:
输入:root = [1,2,3]
输出:6
解释:最优路径是 2 -> 1 -> 3 ,路径和为 2 + 1 + 3 = 6
示例 2:
输入:root = [-10,9,20,null,null,15,7]
输出:42
解释:最优路径是 15 -> 20 -> 7 ,路径和为 15 + 20 + 7 = 42
提示:
树中节点数目范围是 [1, 3 * 104]
-1000 <= Node.val <= 1000
方法一:递归
考虑一个二叉树单元
- a是根节点,与上层的父节点相连(如果有的话)
- b 和 c 是子节点,与其各子节点中路径最大值的节点相连
- 所有可能的路径情况:
a. 左中右 :b + a + c
b. 左 :b + a
c. 右 :c + a
- 选择 左 还是 右
- 实现方法:
a.递归调用 b 和 c
b.计算 b+a 和 c+a,选择较大的值作为返回值
c.更新到全局最大和
class Solution {
private:
int maxSum = INT_MIN;
public:
int maxGain(TreeNode* node) {
if (node == nullptr) {
return 0;
}
// 递归计算左右子节点的最大贡献值
// 只有在最大贡献值大于 0 时,才会选取对应子节点
int leftGain = max(maxGain(node->left), 0);
int rightGain = max(maxGain(node->right), 0);
// 节点的最大路径和取决于该节点的值与该节点的左右子节点的最大贡献值
int priceNewpath = node->val + leftGain + rightGain;
// 更新答案
maxSum = max(maxSum, priceNewpath);
// 返回节点的最大贡献值
return node->val + max(leftGain, rightGain);
}
int maxPathSum(TreeNode* root) {
maxGain(root);
return maxSum;
}
};
复杂度分析
时间复杂度:O(N),其中 N 是二叉树中的节点个数。对每个节点访问不超过 2 次。
空间复杂度:O(N),其中 N 是二叉树中的节点个数。空间复杂度主要取决于递归调用层数,最大层数等于二叉树的高度,最坏情况下,二叉树的高度等于二叉树中的节点个数。
这题目的难点在于理解题意和转化题意。
我们可以结合 数组的最大子数组和 的思路去解题。
-
「可以从任意节点出发, 到达任意节点」 的路径,
一定是先上升( 0 ~ n 个)节点, 到达顶点, 后下降( 0 ~ n 个)节点。
我们可以通过枚举顶点的方式来枚举路径。 -
我们枚举顶点时, 可以把路径分拆成3部分: 左侧路径、右侧路径和顶点。
如下面的路径, 顶点为 20, 左侧路径为 6 -> 15, 右侧为 6 -> 7。以当前节点为顶点的路径中, 最大和为 两侧路径的最大和 + 节点的值。
需要注意的是, 两侧路径也可能不选, 此时取 0。 -
如何求两侧路径最大和? 看一个类似问题:求数组的最大子数组和。
动态规划: dp[i] 代表以 nums[i] 为结尾的子数组的最大和。
转移方程: dp[i] = max(dp[i-1], 0) + nums[i]。 -
在树上, 设 dp[C] 代表以当前节点为结尾的最大上升路径和,
则我们需要对节点的左右子树做一个选择, 有
dp[C] = max(max(dp[L], 0), max(dp[R], 0)) + C.val
式中, C,L,R 分别代指 当前节点、左子节点、右子节点。 -
最后, 以当前节点为顶点的路径中, 最大的和为
max(dp[L], 0) + max(dp[R], 0) + C.val。
我们枚举顶点, 并记录最大答案。