路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。该路径 至少包含一个 节点,且不一定经过根节点。
路径和 是路径中各节点值的总和。
给你一个二叉树的根节点 root ,返回其 最大路径和 。
题解
思想:
题解转载自xiao_ben_zhu
路径每到一个节点,有 3 种选择:1. 停在当前节点。2. 走到左子节点。3. 走到右子节点。
走到子节点,又面临这 3 种选择,递归处理就好。
注意,不能走进一个分支又掉头回来走另一个分支,路径重叠,不符合定义。
定义递归函数
我们关心路径走入一个子树,能从中捞取的最大收益,不用管具体怎么走。
定义dfs函数:返回当前子树能向父节点“提供”的最大路径和。 分为三种情况:
- 路径停在当前子树的根节点,收益:root.val
- 走入左子树,最大收益:root.val + dfs(root.left)
- 走入右子树,最大收益:root.val + dfs(root.right)
这对应了前面讲的三种选择,最大收益取三者中的最大值。
再次提醒: 一条从父节点延伸下来的路径,不能走入左子树又掉头走右子树,不能两头收益。
当遍历到null节点时,返回 0,收益为 0。
如果子树 dfs 结果为负,走入它,收益不增反减,该子树应被忽略,让它返回 0,如同砍掉。
子树中的内部路径要包含根节点
题目说,路径不一定经过根节点,说明,最大路径和可能产生于局部子树中,如下图左一。
因此每递归一个子树,都求当前子树内部的最大路径和,下图右一,比较出最大的。
注意: 一个子树内部的路径,要包含当前子树的根节点。如果不包含,那还算什么属于当前子树的路径,而是当前子树的子树的内部路径。
所以,一**个子树内部的最大路径和 = 左子树提供的最大路径和 + 根节点值 + 右子树提供的最大路径和。**即 dfs(root.left) + root.val + dfs(root.right)
代码实现
注意区分当前子树的内部最大路径和(dfs(root.left)+dfs(root.right)+root.val) 和 当前子树对外所能贡献的最大路径和(root.val+Math.max(leftMax,rightMax),因为要对外走所,不能反复经过root,所以只能取左右子树路径值中的较大值)
class Solution {
int res = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
dfs(root);
return res;
}
public int dfs(TreeNode root){
//递归终止条件:node==null
if(root==null){
return 0;
}
//左子树所能提供的最大值
int leftMax=Math.max(dfs(root.left),0);
int rightMax=Math.max(dfs(root.right),0);
//总的最大路径和=当前子树的内部最大路径和 和 原先记录的最大路径值中较大的那一个
res = Math.max(res,leftMax+rightMax+root.val);
//返回对外所能贡献的路径之和:因为不可走重复节点,所以只能取左右子树中路径值贡献更大的那一条
return root.val+Math.max(leftMax,rightMax);
}
}
复盘总结
递归一个树,会对每个子树做同样的事(你写的处理逻辑)。
通过求出每个子树对外提供的最大路径和,从底而上,求出每个子树内部的最大路径和,后者的求解要用到前者,明白二者的关系。
每个子树的内部最大路径和,都挑战一下最大纪录,递归结束时,最大纪录就有了。
思考递归问题,不要纠结细节实现,思考方式是自顶而下、屏蔽细节的,随着递归出栈,子问题自下而上地解决,最后解决了整个问题,内部细节是子递归实现的。
画出递归树对于递归问题的解决蛮有用,辅助理解。