二叉树最大路径和
题目链接:https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/
分析:
这个题目是求二叉树的最大路径和,要点有两个:
- 最大不能走回头路:从根节点延伸的路径,你不能走了左子树又掉过头来走右子树
- 最大路径不一定要经过根节点
思路:
对每个非叶子节点,其实都面临这样一个问题:求解从此节点开始的最大路径和,所以求解从根节点开始的最大路径和其实可以分解成很多的子问题,这些子问题在根本上其实都是一个问题,有个通用的解法,所以我们可以采取递归的方式,即dfs深度搜索
针对每个节点,其最大收益分三种情况:
- 只走此节点,不走其左右孩子 ,收益:root.val
- 走此节点,并走入其左子树,收益:root.val+dfs(root.left)
- 走此节点,并走入其右子树,收益:root.val+dfs(root.right)
每个节点的收益仅有上面三种情况,对每个节点,取这三种情况的最大值即可:
root.val+max(0, dfs(root.left), dfs(root.right))
这也是当前子树能够提供给外部的最大路径和,即左右孩子只能选择一个走
一个子树内部最大的路径和:左子树的最大路径和+右子树的最大路径和+根节点值,即dfs(root.left)+root.val+dfs(root.right)
上述两种情况,分别是子树提供给外部(其父节点)的最大路径和,子树自身内部的最大路径和
对于每个子问题,我们递归时都需要返回子树提供给外部的最大路径和
而对于子树自身内部最大的路径和,每次都有比较记录一下,取最大值
var result int // 记录最大路径和
// dfs 求从root开始的最大路径和
func dfs(root *TreeNode) int{
// 遍历到nil节点,收益为0
if root==nil{
return 0
}
left:=dfs(root.Left) // 左子树内部提供的最大路径和
right:=dfs(root.Right) // 右子树内部提供的最大路径和
innerMaxSum:=left+root.Val+right // 当前子树内部的最大路径和
result=max(result,innerMaxSum) // 挑战一下最大路径
outputMaxSum:=root.Val+max(0,max(left,right)) // 当前子树对外提供的最大路径和
// 如果子树对外提供的最大路径和为负数,则返回0,因为负数子树对最大路径和毫无贡献
if outputMaxSum<0{
return 0
}
return outputMaxSum // 返回当前子树对外提供的最大路径和,这是每个子问题的解
}
func max(a,b int) int{
if a>b{
return a
}
return b
}
func maxPathSum(root *TreeNode) int {
result=math.MinInt64
dfs(root)
return result
}
耗时分析:
时间复杂度:O(N),每个节点都需要遍历
空间复杂度:O(H),也就是递归的深度,跟树的形状有关
总结:
- 学会把大问题拆解成很多子问题,每个子问题其实都是相似的,子问题有解决方案了,综合一下大问题也就解决了
- 通过求出每个子树对外提供的最大路径和,从递归树底部向上,不断求出每个子树内部的最大路径和(记录最大值),后者是求解的目标,后者的求解需要前者
- 每个子树内部的最大路径和,都挑战一下最大路径和,这样递归结束时,最大路径和也就出来了