树形dp (二叉树中的最大路径、最长距离)
前提
如果题目求解目标是S规则,则求解流程可以定成以每一个节点为头节点的子树在S规则下的每一个答案,并且最终答案一定在其中。
套路:
- 以某个节点X为头节点的子树中,分析答案有哪些可能性,并且这种分析是以X的左子树、X的右子树和X整棵树的角度来考虑可能性的
- 根据第一步的可能性分析,列出所有需要的信息
- 合并第二步的信息,对在树和右树提出同样的要求,并写出信息结构
- 设计递归函数,递归函数是处理以X为头节点的情况下的答案。包括设计递归的basecase,默认直接得到左树和右树的所有信息,以及把可能性做整合,并且要返回第三步的信息结构这四个小步骤
例题:
1、 求二叉树中的最大路径和
给定一个非空二叉树,返回其最大路径和。
本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。
示例 1:
输入: [1,2,3]
1
/ \
2 3
输出: 6
示例 2:
输入: [-10,9,20,null,null,15,7]
-10
/ \
9 20
/ \
15 7
输出: 42
思考:
首先,考虑实现一个简化的函数 maxGain(node),该函数计算二叉树中的一个节点的最大贡献值,具体而言,就是在以该节点为根节点的子树中寻找以该节点为起点的一条路径,使得该路径上的节点值之和最大。
具体而言,该函数的计算如下。
空节点的最大贡献值等于 0。
非空节点的最大贡献值等于节点值与其子节点中的最大贡献值之和(对于叶节点而言,最大贡献值等于节点值)。
根据函数 maxGain 得到每个节点的最大贡献值之后,如何得到二叉树的最大路径和?对于二叉树中的一个节点,该节点的最大路径和取决于该节点的值与该节点的左右子节点的最大贡献值,如果子节点的最大贡献值为正,则计入该节点的最大路径和,否则不计入该节点的最大路径和。维护一个全局变量 maxSum 存储最大路径和,在递归过程中更新 maxSum 的值,最后得到的 maxSum 的值即为二叉树中的最大路径和。
# Definition for a binary tree node. class TreeNode: def __init__(self, x): self.val = x self.left = None self.right = None class Solution: def __init__(self): self.maxSum = float("-inf") def maxPathSum(self, root: TreeNode) -> int: def maxGain(node): if not node: return 0 leftGain = max(maxGain(node.left),0) rightGain = max(maxGain(node.right),0) priceNewPath = node.val+leftGain+rightGain self.maxSum = max(self.maxSum,priceNewPath) return node.val + max(leftGain,rightGain) maxGain(root) return self.maxSum
2、求二叉树中的最大距离
给定一个二叉树,返回其最大路径和。
最大距离定义为,从二叉树的节点a出发,可以向上或者向下走,但沿途的节点只能经过一次,到达节点b时路径上的边的个数叫作a到b的距离,那么二叉树任何两个节点之间都有距离,求整棵树上的最大距离。
思考:
使用树的高度,对于一个节点,自身高度为1
求一棵树中的最长路径。对于节点 t ,以它为根的树的最长路径path一定是下列三个数中的最大值:
- t 的左子树的最长路径 lpath
- t 的右子树的最长路径 rpath
- t 的左子树的高度 + t 的右子树的高度
# Definition for a binary tree node. class TreeNode(object): def __init__(self, x): self.val = x self.left = None self.right = None class Solution(object): def diameterOfBinaryTree(self, root): if not root: return 0 max_dia_left = self.diameterOfBinaryTree(root.left) max_dia_right = self.diameterOfBinaryTree(root.right) max_dia = max(self.get_depth(root.left)+self.get_depth(root.right),max_dia_left,max_dia_right) # max: 1.当前结点最大距离;2.左、右子结点的最大距离 return max_dia def get_depth(self,root): #计算以当前结点为根时,树的最大深度; if not root: return 0 else: return max(1+self.get_depth(root.left),1+self.get_depth(root.right))