「这是我参与11月更文挑战的第 24 天,活动详情查看:2021最后一次更文挑战」
刷算法题,从来不是为了记题,而是练习把实际的问题抽象成具体的数据结构或算法模型,然后利用对应的数据结构或算法模型来进行解题。个人觉得,带着这种思维刷题,不仅能解决面试问题,也能更多的学会在日常工作中思考,如何将实际的场景抽象成相应的算法模型,从而提高代码的质量和性能
二叉树中的最大路径和
题目描述
路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次。该路径 至少包含一个 节点,且不一定经过根节点
路径和 是路径中各节点值的总和。
给你一个二叉树的根节点 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 * 10^4] 1000 <= Node.val <= 1000
解题
解法一:递归
思路
从题意中可以知道,二叉树路径就是
- 从任意结点出发,达到任意结点
- 该路径至少包含一个结点,且不一定经过根节点
比如4 → 2 → 1 → 3;或者6 → 3 → 7;或者路径上只有一个结点2
可以发现,这个路径和二叉树的区别就是:
- 二叉树的一个结点,被一个父节点连接,连接左右两个子节点
- 而路径中,途径一个结点,只能选择来去两个方向(所以,其实就是让我们三选二)
以2这个节点来说,途径2就有下边三种情况:
- 左+右
- 上+左
- 上+右
假设从底往上出发(从4出发)
- 左右子节点只能选一个
- 当走到一个父节点的时候,可以告诉父节点,自己的值是多少,父节点会拿着该子节点的值和另外一个子节点进行比较,选择更大的那个子节点加到自己本身上,然后继续往上走
- 或者不能继续往上走了,则拐弯连接左右子节点
有了上边的大致思路,应该就有解题思路了
假设有一个二叉树单元
-
a是根节点,与上层的父节点相连(假设有的话)
-
b和c是子节点,与其各自子节点中路径最大值的节点相连
-
所有可能的路径情况
- 左中右 b+a+c
- 左 b + a
- 右 c + a
选择左还是右?我们可以定义一个函数,可以计算当前结点通往下方节点的最大路径和
当把a传进去的时候,需要知道b和c通往下方的最大路径和,只需要对b和c进行递归,拿到返回值,取较大的那个,再加上a的值,就得到了a通往下方的最大路径和。需要注意的是,因为整个二叉树的最大路径和,不一定经过根节点,所以,答案并不是根节点的返回值。因此,就需要一个能够记录,所有路径和中最大值的全局变量,当求得某个路径的最大和之后,更新到这个全局最大和中
- 递归调用b和c
- 计算b+a和c+a,选择较大的值作为返回值
- 更新到全局最大和
需要考虑结点值为负数的情况,为了保证和最大,负数应该尽可能的舍弃(可以使用max(0, x))。注意,无论是继续向上还是连接b和c,a作为必经过的地方,是不能舍弃的(防止结点全是负数的情况下,代码就不能获取正确结果了)
代码
//二叉树中的最大路径和
func maxPathSum(root *TreeNode) int {
maxSum := math.MinInt64 //初始化全局最大和
var maxValue func(*TreeNode) int
maxValue = func(node *TreeNode) int { // 用于计算某个节点通往下方的最大路径和
if node == nil {
return 0
}
leftValue := max(maxValue(node.Left), 0) //去除负数的情况
rightValue := max(maxValue(node.Right), 0)
priceNewPath := node.Val + leftValue + rightValue //这两步的目的是为了在全是负数的情况下,不舍弃当前结点本身
maxSum = max(priceNewPath, maxSum)
return node.Val + max(leftValue, rightValue) //选左右节点的最大值加上其本身的值
}
maxValue(root)
return maxSum
}
func max(x, y int) int {
if x > y {
return x
}
return y
}