二叉树染色

标签: 动态规划 二叉树

难度: Medium

小扣有一个根结点为 `root` 的二叉树模型,初始所有结点均为白色,可以用蓝色染料给模型结点染色,模型的每个结点有一个 `val` 价值。小扣出于美观考虑,希望最后二叉树上每个蓝色相连部分的结点个数不能超过 `k` 个,求所有染成蓝色的结点价值总和最大是多少? **示例 1:** > 输入:`root = [5,2,3,4], k = 2` > > 输出:`12` > > 解释:`结点 5、3、4 染成蓝色,获得最大的价值 5+3+4=12` ![image.png](https://pic.leetcode-cn.com/1616126267-BqaCRj-image.png) **示例 2:** > 输入:`root = [4,1,3,9,null,null,2], k = 2` > > 输出:`16` > > 解释:结点 4、3、9 染成蓝色,获得最大的价值 4+3+9=16 ![image.png](https://pic.leetcode-cn.com/1616126301-gJbhba-image.png) **提示:** + `1 <= k <= 10` + `1 <= val <= 10000` + `1 <= 结点数量 <= 10000`

Submission

运行时间: 324 ms

内存: 22.6 MB

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def maxValue(self, root: TreeNode, k: int) -> int:

        def maxSubTree(node):
            if not node:
                return [0]

            l = maxSubTree(node.left)
            r = maxSubTree(node.right)

            res = [0] * (k + 1)
            res[0] = l[-1] + r[-1]
            for i in range(len(l)):
                for j in range(min(len(r), k - i)):
                    res[i + j + 1] = max(res[i + j], res[i + j + 1], l[i] + r[j] + node.val)
            return res[: len(l) + len(r)]
        
        return max(maxSubTree(root))

Explain

该题解采用递归方法进行动态规划。首先定义一个辅助函数 maxSubTree,该函数返回一个列表,其中包含以当前节点为根的子树内,各个可能的蓝色节点群组的最大值累计。列表中的每个位置i表示恰好有i个连续节点被染色的情况下,可以获得的最大价值。函数首先处理基本情况,如果当前节点为空,则返回一个只包含0的列表。然后,它递归地计算左右子树的最大价值列表。使用两层循环遍历左右子树返回的列表,来确定在当前节点下,形成不同大小的蓝色节点群组的最大价值。外层循环和内层循环分别遍历左子树和右子树返回的列表,通过将左右子树的某些价值组合并加上当前节点的价值,来更新结果列表。最后,返回整棵树调用 maxSubTree 后的最大价值。

时间复杂度: O(n*k^2)

空间复杂度: O(n*k)

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def maxValue(self, root: TreeNode, k: int) -> int:

        def maxSubTree(node):
            # 如果节点为空,返回包含一个0的列表
            if not node:
                return [0]

            # 递归计算左右子树的最大价值列表
            l = maxSubTree(node.left)
            r = maxSubTree(node.right)

            # 初始化当前节点的最大价值列表
            res = [0] * (k + 1)
            res[0] = l[-1] + r[-1]
            # 更新当前节点下各种可能的连续蓝色节点群组的最大价值
            for i in range(len(l)):
                for j in range(min(len(r), k - i)):
                    res[i + j + 1] = max(res[i + j], res[i + j + 1], l[i] + r[j] + node.val)
            return res[: len(l) + len(r)]
        
        # 返回整棵树的最大价值
        return max(maxSubTree(root))

Explore

在递归函数`maxSubTree`中,当节点为null时返回包含一个0的列表,这里的0代表的是,在没有任何节点的情况下,染色节点个数为0时的最大价值。因为没有节点可染色,所以最大价值是0。这个返回值为递归处理提供了一个基准情况,确保当递归到空节点时,可以正常地继续计算而不会出现错误或异常。

在计算`res[0]`时使用`l[-1] + r[-1]`,这个操作代表的是计算当前节点不被染色时,其左右子树能够达到的最大价值的和。这里选择列表的最后一个元素进行相加是因为,列表的最后一个元素(即`l[k]`和`r[k]`)代表了在子树中恰好有k个连续节点被染成蓝色时的最大价值。因此,当当前节点不染色时,我们希望利用左右子树分别达到其最大染色潜力的和,这就是`l[-1]`和`r[-1]`的含义。

在更新`res`数组时,内层循环的终止条件设置为`k - i`是为了确保总的染色节点数不超过k。这里`i`表示从左子树中选取的染色节点数,而`j`表示从右子树中选取的染色节点数。因此,`i + j + 1`(加1表示当前节点也被染色)是当前节点下形成的蓝色节点群组的总数,这个值必须小于等于k。通过设置循环的终止条件为`k - i`,我们确保了在任何时候,左子树和右子树中选取的节点数之和不会超过k减去已选择的左子树节点数,从而保证整个节点群组的大小符合要求。