[路飞]算法:监控二叉树

208 阅读5分钟

968. 监控二叉树

正题

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

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

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

示例 1:

image.png

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

示例 2:

image.png

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

解析:

这是一个困难难度的算法题,核心在于二叉树的遍历以及判断各个节点是否被监控的状态。我们需要求得的是二叉树的所有节点都属于监控状态的时候,所要安装的“监控器”的最小数量树多少。

一.考虑遍历方式

求二叉树的各种问题首先第一步就是要考虑如何遍历二叉树。假设我们已知父节点被监控,那么我们能否推导出其左右子节点是否被监控到了呢?

image.png

如图,我们可以看出 A节点已经被监控到了,那么 B,C的状态又如何确定呢?目前看来是没有被监控到的,如果我需要再次将B,C节点监控,最好的方式就是在A上面再放一个“监控”,很显然,A被监控了2次,违背了最小数量原则。所以从父节点去推断子节点状态并且找到监控器安装位置的方法是很困难的。

那么换一个思路,我们通过子节点去推导父节点是否可行?

image.png

如图,我们如果知道B,C都被监控到了,那么我们可以考虑A不去安装监控器了,更有可能B或C中安装了监控器的话,A的监控状态是“已被监控”也可以轻易知道,所以我们更加倾向于从子节点去推导父节点的状态,因此我们考虑使用后序遍历的方法去遍历二叉树,然后逐个判断状态。

有哪些状态?

每个节点非常明显的两个状态,未被监控和已被监控。但是正如我们之前推导过程看来,假设B安装了摄像头,那么我可以推导出A的状态也会是已被监控,所以我们在看已被监控应该分为两种,第一种是被其他节点摄像头监控,第二种是自己安装了摄像头。所以我们应该设置3种状态:分别用 0-2表示:

0 - 未被监控

1 - 已被监控

2- 这个节点安装了摄像头(用来推导父节点也是已被监控的状态)

实现后序遍历

首先实现后序遍历 helper 方法:

function helper (node) {
    const left = helper(node.left)
    const right = helper(node.right)
}

通过递归方式,实现二叉树的后序遍历,那么产生的节点就是由子到父了。

核心:推导状态

  1. 当遍历的子节点不存在时:

后序遍历的递归算法到 node === null 的时候跳出,那么我们可以认为这个节点是不存在的,不存在的节点不需要监控,换句话说,我们可以认为他已经被监控即可,所以 node === null 时,我们返回的状态是 1

  1. 当左右节点都被监控时:

left === 1 && right === 1,那么我们认为当前节点没有被监控即下图所示

image.png

可以发现,BC都被监控覆盖了,但是BC都没有安装摄像头,那么他们的父节点A就是未被监控的状态,因此我们返回的状态是 0

3.当左右节点有一个安装了摄像头时

image.png

如图,可以发现,无论是B还是C安装了摄像头,那么A都会是已被监控的状态,所以返回状态是 1。是否被监控只有两种情况,要么安装了摄像头,要么在别的摄像头区域内,要保证ABC都被监控,必须是 B 的状态 + C 的状态 <= 3,即BC都被监控,且至少有一个安装了摄像头。

4.剩下的所有状态都是需要安装摄像头的(即左右有一个被监控,另一个没有被监控,或两个都没有被监控)

这种情况下我们是需要安装摄像头在父节点的。所以返回状态是 2

总结:

  1. root === null 返回 1
  2. left === 1 && right === 1 返回 0
  3. left + right >= 3 返回 1
  4. 剩下所有情况 返回2,并且摄像头数量 +1

代码实现:

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
var minCameraCover = function(root) {
    if (root === null) {
        return 0
    }
    let count = 0 // 记录摄像头数量
    
    function helper (node) {
        if (node === null) {
            return 1 // 没有该节点了,我们可以认为他是不需要被监控,所以 return 1 表示已经被监控
        }
        const left = helper(node.left) // 后序遍历
        const right = helper(node.right) // 后序遍历
        if (left === 1 && right === 1) {
            // 左右节点都被监控,且没有安装摄像头,自己就是未被监控状态
            return 0
        }
        if (left + right >= 3) {
            // 左右节点都被监控,且至少有一个安装了摄像头
            return 1
        }
        // 左右没有被完全监控,自己必须安装摄像头
        count++ // 摄像头数量 +1
        return 2
    }
    let a = helper(root)
    if (a === 0) {
        // 根节点没有被监控的话还需要在根节点设置一个摄像头
        count++
    }
    return count
};

代码注释写的比较清楚,题目比较困难,可以参考备注进行理解。本题实际上也是二叉树的动态规划求解问题。