力扣-968监控二叉树(Java贪心详细题解)

题目链接:968. 监控二叉树 - 力扣(LeetCode)

前情提要:

本题是一道名副其实的hard题目,他考察二叉树和贪心的综合运用能力。

所以我们不仅要会贪心还要会二叉树的一些知识,如果没有写二叉树类型的题目,建议大家该题可以放放,去刷其他的题目。

因为本人最近都来刷贪心类的题目所以该题就默认用贪心方法来做。

贪心方法:局部最优推出全局最优。

如果一个题你觉得可以用局部最优推出全局最优,并且没有反例来反驳的话就可以用贪心来试试。

题目思路:

本题要求监控树所有节点的最小摄像头数量。我们如何求这个最小摄像头数量呢?

一般遇到二叉树类的题目,遍历顺序很重要,我们如何确认这个遍历顺序呢?

确认遍历顺序

其实从案例我们也会发现,摄像头都没有放在叶子节点,而是放在了叶子节点的父节点。

首先我们要知道一个摄像头所监控的最大范围就是三层。

那么我们怎么最大限度的使用这个条件呢?这就是贪心所在。

我们尽量在叶子节点的父节点设为摄像头 这样可以最大限度的使用摄像头监控三层的条件。

以这个题局部贪心:再每一个叶子节点前面放一个摄像头,再每隔俩个节点再放一个摄像头。

全局最优:所得的摄像头的就是最少的。

那么肯定有人想叶子节点的父节点设摄像头 那我们可不可以在根节点的孩子节点设摄像头呢?

理论也行,但是本题要求最少的摄像头,我们就要最大限度的节省摄像头,由于一个二叉树叶子节点肯定大于等于根节点,处于节约叶子节点的摄像头而言,我们应该从叶子节点出发。

遍历顺序咱们就定下来 就是后序 从叶子节点向上返回结果为父节点。

那么我们如何隔两个节点放一个摄像头

此时需要分析每个节点的状态 三种情况的判定 0 无覆盖 1摄像头 2覆盖。

所以我们得根据左右孩子节点的状态来确认父节点的状态。

但是我们要对二叉树内的null值进行一个特判,null值应该赋给什么状态呢? 答案是赋值为2。为什么赋值为2呢。我们下面讲。

1.左右孩子节点全为2状态 父节点就为 0。

在这里插入图片描述

这里可能互相会想为什么状态为0 为什么不是1 这是因为我们是后序遍历,回溯的过程是从下到上,当孩子节点状态为2 肯定是孩子节点的下层节点出现了 1 此时我们本着最大节约摄像头的原则,他们的父节点就为0,由父节点的父节点为摄像头来将父节点覆盖。

这时我们就想通为什么null值设为2了,当一个孩子为叶子节点,他下一层返回的状态就是俩个2,那他本身应该就是未被覆盖的状态,因为本题的贪心思路就是让叶子节点都为非覆盖状态,让他的父节点为摄像头来监控下面三层的情况。

2.任意孩子节点为1 父节点就是2。

如果任意一个孩子节点被摄像头监控,那父节点就是被覆盖的范围,因为摄像头监控的范围未3层,他可以覆盖他上一层的也就覆盖父节点。

3.任意孩子为 0 父节点就是1。

在这里插入图片描述

如果是以下情况,则中间节点(父节点)应该放摄像头:

  • left == 0 && right == 0 左右节点无覆盖
  • left == 1 && right == 0 左节点有摄像头,右节点无覆盖
  • left == 0 && right == 1 左节点有无覆盖,右节点摄像头
  • left == 0 && right == 2 左节点无覆盖,右节点覆盖
  • left == 2 && right == 0 左节点覆盖,右节点无覆盖

这个不难理解,毕竟有一个孩子没有覆盖,父节点就应该放摄像头。

特判第四种方式 如果根节点为无覆盖 则将根节点设为1 即设为摄像头。

在这里插入图片描述

所以状态分析完了就可以开始写代码了

最终代码:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    //result用来记录结果
    int result = 0;
    public int minCameraCover(TreeNode root) {
        //temp用来判定第四种情况
        int temp = dfs(root);
        if(temp == 0){
            result ++;
        }
        return result;
        
    }
    public int dfs(TreeNode root){
        //递归三部曲
        //第一步确定方法的返回值和参数
        //后序需要子节点的状态来确认父节点的状态,所以我们需要将返回值设为int
        //第二步确定终止条件
        //遍历树最终会遇到null节点 所以终止条件就是遇到null就不要向下递归了 就该向上返回状态值了
        //第三步确定单层循环逻辑
        if(root == null)return 2;
        //遍历顺序 后序 左右中 即先递归左边 再递归右边 最后处理中间节点
        //左
        int left = dfs(root.left);
        //右
        int right = dfs(root.right);
        //三种情况的判定 0 无覆盖 1摄像头 2覆盖
        //1.当孩子节点全为 2 父节点就为0
        //2.当孩子节点有一个为0 父节点就为1
        //3.当孩子节点有一个为1 父节点就为 2
        if(left == 2&&right == 2)return 0;
        if(left == 0 ||right == 0){
            result ++;
            return 1;
        }
        if(left == 1 ||right == 1)return 2;
        return 0;
    }
}

这到题确实很难,所以需要大家多琢磨琢磨,多模拟几遍。

这一篇博客就到这了,如果你有什么疑问和想法可以打在评论区,或者私信我。

我很乐意为你解答。那么我们下篇再见!

  • 18
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值