[路飞]_监控二叉树

147 阅读6分钟

leetcode-968 监控二叉树
b站视频

题目介绍

给定一个二叉树,我们在树的节点上安装摄像头。

节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。

计算监控树的所有节点所需的最小摄像头数量。

示例1

image.png

输入: [0,0,null,0,0]
输出: 1
解释: 如图所示,一台摄像头足以监控所有节点。

示例2

image.png

输入:[0,0,null,0,null,0,null,null,0]
输出:2
解释:需要至少两个摄像头来监视树的所有节点。 上图显示了摄像头放置的有效位置之一。

提示:

  1. 给定树的节点数的范围是 [1, 1000]
  2. 每个节点的值都是 0。

解题思路

这道题需要通过动态规划的方法来做,每一棵树的最小放置摄像头的数量又取决于其子树的放置摄像头的数量,动态规划的实现方法也是通过递归来实现的

因此,首先我们定义一个二维数组 dp,二维数组的每一值的含义是这样的:

  • dp[0][0] 父节点和当前节点都没有放置摄像头
  • dp[0][1] 父节点没有放置摄像头,当前节点放置摄像头
  • dp[1][0] 父节点放置摄像头,当前节点没有放置摄像头
  • dp[1][1] 父节点和当前节点都放置摄像头

接下来我们看一下当前节点的 4 种情况

1642094433(1).png 如果当前节点是空节点

  • dp[0][0] 表示父节点和当前节点不放置摄像头的情况下,覆盖当前节点的摄像头最小数量为 0,因为当前是空节点,不需要摄像头来覆盖
  • dp[0][1] 表示父节点不放置摄像头,当前节点放置摄像头。因为当前节点是空节点,不可能放置摄像头,因此此处用一个不可能取到的值 1001 来表示不可能事件
  • dp[1][0] 表示父节点放置摄像头,当前节点不放置摄像头的情况下,覆盖当前节点的摄像头最小数量为 0,原因和 dp[0][0] 一致
  • dp[1][1] 表示父节点和当前节点都放置摄像头为 1001,原因和 dp[0][1] 一致

1642094902(1).png 如果当前节点是叶子节点

  • dp[0][0] 表示父节点和当前节点不放置摄像头的情况下,覆盖当前节点是不可能发事件,用 1001 表示
  • dp[0][1] 表示父节点不放置摄像头,当前节点放置摄像头时,覆盖当前节点的最小摄像头数量是 1,即当前节点自身的摄像头
  • dp[1][0] 表示父节点放置摄像头,当前节点不放置摄像头的情况下,覆盖当前节点的摄像头最小数量为 0,因为父节点的摄像头已经能够覆盖当前节点
  • dp[1][1] 表示父节点和当前节点都放置摄像头的情况下,覆盖当前节点的最小摄像头数量为 1,即当前节点自身的摄像头

1642095200(1).png 如果当前节点是中间节点,覆盖当前节点的最小摄像头数量与其子树的最小摄像头数量有关,定义 l 表示其左子树 4 种情况下的最小摄像头放置数量,r 表示其右子树 4 种情况下的最小摄像头放置数量

  • dp[0][0] 表示父节点和当前节点不放置摄像头的情况下,覆盖当前节点时需要至少一个子节点放置摄像头,可能是左节点,可能是右节点,也可能两个节点都放置,因此取其最小值为 Math.min(l[0][1] + r[0][0], l[0][0] + r[0][1], l[0][1] + r[0][1])
  • dp[0][1] 表示父节点不放置摄像头,当前节点放置摄像头。因为当前节点已经被自己的摄像头覆盖,所以子节点放不放置摄像头都没问题,所以其最小值为 Math.min(l[1][0] + r[1][0], l[1][1] + r[1][0], l[1][0] + r[1][1], l[1][1] + r[1][1]) + 1,最后 + 1 是加上当前节点自身的摄像头数量
  • dp[1][0] 表示父节点放置摄像头,当前节点不放置摄像头时。因为当前节点已经被父节点的摄像头覆盖,其子节点放不放置摄像头都行,所以最小值是 Math.min(dp[0][0], l[0][0] + r[0][0]),在 dp[0][0] 的基础上还有左右子节点都不放置摄像头的情况
  • dp[1][1] 表示父节点和当前节点都放置摄像头的情况下。原因和 dp[1][1] 一致,因此最小值也和 dp[1][1] 相同

1642095928(1).png 如果当前节点是根节点,因为根节点没有父节点,所以只需要取 dp[0][0]dp[0][1] 的最小值

解题代码

var minCameraCover = function(root) {
    // 定义一个二维数组,代表父节点与当前节点的 4 种摆放摄像头的情况下,覆盖当前节点所需要的摄像头数量
    // dp[0][0] 父节点和当前节点都没有放置摄像头
    // dp[0][1] 父节点没有放置摄像头,当前节点放置摄像头
    // dp[1][0] 父节点放置摄像头,当前节点没有放置摄像头
    // dp[1][1] 父节点和当前节点都放置摄像头
    const dp = [[0, 0], [0, 0]]
    // 动态规划获取 dp 的值
    DP(root, dp)
    // 获取 监控树的最小摄像头数量
    return Math.min(dp[0][1], dp[0][0])
};

var DP = function(root, dp) {
    // 如果当前节点为空节点,dp 值的 4 种情况
    // 1000 表示不可能存在的现象,因为树的节点数最大是 1000,当我们取最小值时,绝对不可能取到 1000,因此用 1000 来表示不可能现象
    if (!root) {
        dp[0][0] = 0
        dp[0][1] = 1000
        dp[1][0] = 0
        dp[1][1] = 1000
        return
    }
    // 如果当前节点是叶子节点,dp 值的 4 种情况
    if (!root.left && !root.right) {
        dp[0][0] = 1000
        dp[0][1] = 1
        dp[1][0] = 0
        dp[1][1] = 1
        return
    }
    // 分别获取左、右子树 dp 值的 4 种情况
    const l = [[0, 0], [0, 0]], r = [[0, 0], [0, 0]]
    DP(root.left, l)
    DP(root.right, r)
    // 当前节点的最小摄像头数量取决于其子树的最小摄像头数量
    // 如果父节点和当前节点没有放置摄像头,那么必须至少有一个子节点放置摄像头
    dp[0][0] = Math.min(l[0][0] + r[0][1], l[0][1] + r[0][0], l[0][1] + r[0][1])
    // 如果父节点没有放置摄像头,当前节点放置了摄像头,那么子节点放不放置摄像头都行,最后加 1 是当前节点放置的摄像头
    dp[0][1] = Math.min(l[1][0] + r[1][0], l[1][1] + r[1][0], l[1][0] + r[1][1], l[1][1] + r[1][1]) + 1
    // 如果父节点放置摄像头,当前节点没有放置摄像头,那么子节点放不放置摄像头都行
    dp[1][0] = Math.min(dp[0][0], l[0][0] + r[0][0])
    // 如果父节点和当前节点都放置了摄像头,那么子节点放不放置摄像头都行,情况和 dp[0][1] 一致
    dp[1][1] = dp[0][1]
    return
}

以上是本题利用动态规划的解题过程,配合视频的讲解应该可以更好的理解
b站视频