[路飞]_一起刷leetcode 968. 监控二叉树

大家好,我是挨打的阿木木,爱好算法的前端摸鱼老。最近会频繁给大家分享我刷算法题过程中的思路和心得。如果你也是想提高逼格的摸鱼老,欢迎关注我,一起学习。

题目

968. 监控二叉树

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

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

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

示例 1:

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

示例 2:

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


提示:

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

思路

  1. 要想实现每个节点都监控,那么我们要么监控当前节点,要么监控当前节点的某个子节点;
  2. 题目中要找到最小摄像头数量,所以我们可以用动态规划, 确保到达每个层级、每个节点的时候我们的解都是最优解,这样子到达最后一层的最后一个节点的时候,我们的答案自然是最佳答案;
  3. 那么动态规划就涉及到状态转移方程,举个例子,我们想要监听一个如下的树结构的根节点,那么我们就需要监听根节点或者监听左右两个子节点其中之一;

image.png

  1. 如果左右两个子节点还有子节点,那么可以分成几种情况: 在1处放置摄像头,然后再计算4-5-6三个节点需要花费多少个摄像头,或者在2处放摄像头,然后再计算3-5-6这三个节点需要花费多少个摄像头,或者在3处放摄像头,然后计算2-4需要花费多少个摄像头,只有这三种情况没有第四种了, 那么我们可以求这三种情况的最小值;

image.png

  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, isMust = false, isCover = false) {
    if (!root) {
        return 0;
    }

    // 根据是否有子节点进行分类讨论
    if (!root.left && !root.right) {
        // 没有子节点,说明当前的监控完就完事了
        // 如果没监控过就加个摄像头监听当前节点
        return isCover ? 0 : 1;
    } else if (!root.left || !root.right) {
        // 如果必须监控的话
        if (isMust) {
            return minCameraCover(root.left || root.right, false, true) + 1;
        }

        // 如果已经监控过的话
        if (isCover) {
            return Math.min(
                minCameraCover(root.left || root.right, false, false),
                minCameraCover(root.left || root.right, false, true) + 1
            );
        }

        // 如果只有一个子节点,这一轮没监控那么就必须子节点监控
        return Math.min(
            minCameraCover(root.left || root.right, false, true) + 1,
            minCameraCover(root.left || root.right, true, false),
        )
    } else {
        // 如果必须监控的话
        if (isMust) {
            return minCameraCover(root.left, false, true) + 
                minCameraCover(root.right, false, true) + 1;
        }

        // 如果已经监控过的话
        if (isCover) {
            return Math.min(minCameraCover(root.left, false, false) + 
                    minCameraCover(root.right, false, false),
                    minCameraCover(root.left, false, true) + 
                    minCameraCover(root.right, false, true) + 1);
        }


        // 如果有两个子节点,就从中二选一,或者直接监控根节点
        return Math.min(
            ...[
                minCameraCover(root.left, false, true) + 
                minCameraCover(root.right, false, true) + 1,
                minCameraCover(root.left, true, false) +
                minCameraCover(root.right, false, false),
                minCameraCover(root.left, false, false) + 
                minCameraCover(root.right, true, false),
            ]
        )
    }
};
复制代码

翻车

image.png

好不容易整理完思路,把代码咔咔一通敲出来,结果发现超时了。尴尬,而且写了很多冗余代码,先精简一版看看行不行。

精简代码

/**
 * @param {TreeNode} root
 * @return {number}
 */
var minCameraCover = function(root, isMust = false, isCover = false) {
    if (!root) {
        return 0;
    }

    // 根据是否有子节点进行分类讨论
    if (!root.left && !root.right) {
        // 没有子节点,说明当前的监控完就完事了
        // 如果没监控过就加个摄像头监听当前节点
        return isCover ? 0 : 1;
    } else {
        const isLose = !root.left || !root.right;
        // 如果必须监控的话
        if (isMust) {
            return minCameraCover(root.left, false, true) + 
                minCameraCover(root.right, false, true) + 1;
        }

        // 如果已经监控过的话
        if (isCover) {
            return  Math.min(minCameraCover(root.left, false, false) + 
                    minCameraCover(root.right, false, false),
                    minCameraCover(root.left, false, true) + 
                    minCameraCover(root.right, false, true) + 1);
        }


        // 如果只有一个子节点,这一轮没监控那么就必须子节点监控
        // 如果有两个子节点,就从中二选一,或者直接监控根节点
        return isLose ? 
            Math.min(
                minCameraCover(root.left || root.right, false, true) + 1,
                minCameraCover(root.left || root.right, true, false),
            )
            : Math.min(
            ...[
                minCameraCover(root.left, false, true) + 
                minCameraCover(root.right, false, true) + 1,
                minCameraCover(root.left, true, false) +
                minCameraCover(root.right, false, false),
                minCameraCover(root.left, false, false) + 
                minCameraCover(root.right, true, false),
            ]
        )
    }
};
复制代码

再次翻车

image.png

这个版本的代码以及精简了很多了,可读性方面已经勉强可以接受了,但是还是超时,说明我们直接递归是走不通的,所以我们要找规律。其实我们自上而下的过程中,需要对比每一个节点是否最优,相当于每次要自上而下把每一条可以走的路全部走一遍,这样子性能是最低的。所以我们换种思路,自下而上走,思路是一样的。

再次起航

  1. 如果节点没有子节点,那么可以在自己身上挂摄像头,也可以在父节点身上挂摄像头,我们用贪心的思想,想求最少的摄像头,那么能在父节点挂摄像头就别挂在子节点身上;
  2. 我们可以用三个数字 0 - 1 - 2 来标识各个节点的监控情况, 0 代表没有被监控,如果当前节点为0,那么得考虑它有没有子元素,如果没有子元素可以在它父节点身上挂个摄像头;
  3. 换位思考一下,每次我们只需要判断当前节点有没有未被监控的子节点,如果有的话就给当前节点挂摄像头,挂了摄像头的话我们需要统计数量,把节点的摄像头数量累加;
  4. 如果当前节点挂了摄像头,那么当前节点的值用2来标识,父节点可以判断子节点中是否存在摄像头,如果存在那么父节点可以用1来表示已经监控过了,不过判断之前得确保,另一个节点是被监控状态的,也就是另一个子节点的值不能是0
  5. 在子节点不为0的情况下,当前节点如果是1则不用进行操作;
  6. 最终每个节点的最优解,可以转换为左节点的最优解+右节点的最优解,如果当前需要监控就再加一。

最终代码

/**
 * @param {TreeNode} root
 * @return {number}
 */
var minCameraCover = function(root) {
    let result = dfs(root);

    // 递归后判断当前摄像头还是否需要监控
    if (root && root.val === 0) {
        result++
    }

    return result;
};

// 遍历二叉树,找子节点是否存在摄像头
function dfs(node) {
    if (!node) {
        return 0;
    }

    // 后序遍历,先访问左右子节点,判断他们是否被监控
    const left = dfs(node.left);
    const right = dfs(node.right);

    let result = left + right;

    // 约定好,摄像头的三个状态
    // 0代表没有被监控到
    // 1代表被监控到了
    // 2代表装了摄像头

    // 子节点没有被监控到的时候说明该父节点出马了
    if (node.left && node.left.val === 0 || node.right && node.right.val === 0) {
        node.val = 2;
        result++;
    } else  // 子节点装了摄像头说明父节点也可以监控到
    if (node.left && node.left.val === 2 || node.right && node.right.val === 2) {
        node.val = 1;
    }

    return result;
}
复制代码

看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。

猜你喜欢

转载自juejin.im/post/7041555649783660557