题目链接: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;
}
}
这到题确实很难,所以需要大家多琢磨琢磨,多模拟几遍。
这一篇博客就到这了,如果你有什么疑问和想法可以打在评论区,或者私信我。
我很乐意为你解答。那么我们下篇再见!