写在前面:
又是一周的周末啦,题主当然又是被迫与同学们参加了很多社交活动。周五去参加了 USC 电影学院同学的电影放映分享,感叹与我同龄的人都已经成为小小制片老板了我还在哭兮兮的敲代码~~这就是人与人之间的残差嘛——
周六晚上是 USC Trojans Football 校队的第一场比赛,继上一场在 Las Vegas 拿下全美第 13 LSU 之后,我们在这个赛季的一个主场也是 48 - 0 零封了 Utah State。Trojans 今年有机会在新的联赛跻身全美 12 强季后赛,争夺一下赛区冠军和全国冠军呢!!Trojans Fight On!
OK 题归正传,今天我们正式进入 Tree 类的题目分享,今天这道题目是属于比较难,但比较代表性的 Tree 题目代表。来源于比较古早的 Leetcode 题目,做对他做会他对理解 recursion 和 Tree 类的问题非常有帮助,就让我们一起来看看吧!
题目介绍:
题目信息:
- 题目链接:https://leetcode.com/problems/binary-tree-maximum-path-sum/description/
- 题目类型:Tree,DFS, Recursion
- 题目难度: Hard
- 题目来源:Google 高频面试题
题目问题:
- 给定一个 Tree,每一个 TreeNode 都有固定的一个 value
- Path Sum 值得是在一段 traverse 路径下,所有的 TreeNode val 的累加综合
- 求:最大的 Path Sum
- 限定条件:每一个 Node 只能出现在一种 Path Sum 中,不可以重复计算
题目想法:
Brute Force:
我们首先可以想到,遍历所有的树的节点,然后针对每一个节点,找出所有和他相连的可能的 node,组成所有可能的 path,再在这些 path 中选择最大的 PathSum 作为答案。虽然这是一种可行的解决方法,但是 Runtime 会是 遍历整个树 O(n), 乘上每一个点都要再遍历一次树 O(n) 成为 O(n^2), 上来说并不优秀,并且我们其实做了很多重复的计算,例如当我们计算一个子节点的时候,他的父节点能选择的 PathSum 其实就只有两个 子节点 PathSum 更大的那一个加上他自己,并不需要再对所有的点进行一次遍历。所以我们可以针对这个特性对寻找答案进行一个优化
优化想法:
我们首先想到,在当前节点 root 下,他所可能产生的最大 PathSum 有四种可能来源:
- 他的 PathSum 来源于 left subtree 最大sum + right subtree 最大 sum + 自己
- 他的 PathSum 来源于 left subtree 最大sum + 自己
- 他的 PathSum 来源于 right subtree 最大sum + 自己
- 他的 PathSum 来源于 他自己
这就意味着,我们在每一个节点,都需要获得他的 两个子树的最大获得,即在这个子树下能产生的最大获得是多少。
同时,因为TreeNode 的 value 有正有负,并且我们在考虑他们的加法最大,所以,我们可以直接忽略掉负数的贡献,因为他们一定会拉低数值。所以,在考虑单一 subtree 对最大 sum 的贡献时,我们只需要考虑 >= 0 子树元素的贡献即可。
既然这样,我们可以利用 bottom-up 的 recursion 方法,计算每一层的 左右子树 所能提供的最大 sum 获得,去掉小于 0 的部分。又因为如果上层需要使用当前 root 作为路径,他只能选取当前 root 的其中一个 subtree 作为 gain。所以,在当前 root 能为上一层所贡献的最大 sum可以表示为:
max(0, leftSubTreeGain) + root->val, max(0, rightSubTreeGain) + root->val))
再者,每一个 root 在自己层级,都可能可以 form 一个 最大 PathSum 的 path 出来,所以我们需要一个全局变量 MaxSum,在每次计算当前层的 左右最大贡献以后,计算并更新在当前层所能产生的最大 MaxSum 的值:
maxSum = max(maxSum, (leftMax + rightMax + root->val))
注意:
MaxSum 和 return 到再上一层的 MaxGain 是不同的概念:
- MaxSum 指使用当前节点和以下层可以产生的最大 PathSum 结果,是直接的结果
- MaxGain 指上层使用当前或以下节点可以获得的最大 Sum 的路径和,是部分的结果
如果大家觉的我的文字讲解不够清晰的话,Github 上有带有图示讲解的详细答案,可以从以下访问:https://leetcode.com/problems/binary-tree-maximum-path-sum/editorial/
题目解法:
- 定义 MaxSum 全局变量为 INT_MIN
- traverse 整个树:
- 在每一个 head节点中:
- maxLeftGain = max(0, gain(head->left))
- maxRightGain = max(0, gain(head->right))
- maxSum = max(maxSum, maxLeftGain + maxRIghtGain + head->val)
- 返回 次节点最大路径获得 max(maxLeftGain, maxRightGain) + head->val
- 在每一个 head节点中:
- 返回 MaxSum
题目代码:
/**
* 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 maxSum = INT_MIN;
int getMaxSumPath(TreeNode* head){
//there is no route
if(head == nullptr){
return 0;
}
//traverse left, and right, we only consider the contribution >= 0 to be considered
int gainFromLeft = max(0, getMaxSumPath(head->left));
int gainFromRight = max(0, getMaxSumPath(head->right));
//determine whether we should update the maxSum from current path:
//a route is consist of left subtree, right subtree and itself:
maxSum = max(maxSum, gainFromLeft + gainFromRight + head->val);
//return the current gain to the upper level:
return max(gainFromLeft + head->val, gainFromRight + head->val);
}
int maxPathSum(TreeNode* root) {
getMaxSumPath(root);
return maxSum;
}
};
- Runtime: O(N)
- Space: O(1)