一、怎样让所需的摄像头数量最少?
因为要让所需的摄像头数量最少,而摄像头可以覆盖上中下三层,因此如果把摄像头放在叶子节点上,就会浪费一层的覆盖。
所以要让所需的摄像头数量最少,我们就选择在叶子节点的父节点安摄像头,然后隔一个节点放一个摄像头,直至到二叉树头结点。自底向上因此使用后序遍历。
哪里贪心了?
局部最优:让叶子节点的父节点安摄像头,所用摄像头最少。整体最优:全部摄像头数量所用最少!
那么问题来了,怎么在后序遍历的过程中放摄像头呢?
二、怎样放摄像头?
因为我们是自下往上遍历,因此遍历到某节点时放不放摄像头,是要根据其下面的子结点的状态(子结点是否安了摄像头、是否被其下层摄像头覆盖了)来决定的。
自下往上 每个节点返回的的状态 有三种,我们分别用三个数字表示,返回对应的数字就表示返回给上层对应状态:
0、无覆盖(其下层没有摄像头能覆盖它)
1、有摄像头(自己这个位置上安了摄像头)
2、有覆盖(被下层的其它摄像头覆盖了)
用全局变量res
记录最少使用的摄像头数。
三、后序遍历递归的逻辑
(1)确定返回值即参数:
因为每个节点要返回自己的状态给上一层,而状态我们用了三个数字表示,因此返回值为int,参数就传一个节点就行
(2)递归结束的逻辑:
因为我们到叶子结点时就要开始向上逐层返回了,因此结束逻辑就是返回叶子结点的状态。
而叶子节点的状态也一样要由其下层(空节点)返回的状态决定,那么空节点返回什么状态呢?
——— 2有覆盖状态。返回1有摄像头状态肯定不行,没事给空节点加什么摄像头,浪费。那么返回0无覆盖状态行不行呢?也不行,因为若空节点返回了0无覆盖状态给上层的叶子节点,则叶子节点就需要设置摄像头去覆盖空节点,因此只能是2状态。
【也正因为此,树就不需要分只有左节点或只有右结点以及左右节点都有的情况讨论了,因为null空节点也会返回一个值,因此就可以统一进行处理而不需要分类讨论了】
(3)单层递归的逻辑:
单层递归处理什么?—— 子结点向上返回状态之后,父节点需要根据它们返回的状态来决定自己目前的状态。
(为什么说是目前的状态?因为随着之后父节点也向上层返回继续从下往上走的过程中,上层可能会安摄像头,有可能就会覆盖这个父节点。但是我们看的是当前这层的递归逻辑,此时还没返回,即父节点的上层都还没构建,因此说是决定目前的状态)
处理情况有三种:
(1)左右子结点都有覆盖(返回状态都为2)
则当前节点返回当前状态0。
因为其子结点都被别人覆盖完了,自己就不需要再去考虑覆盖它们了,至于自己之后是否会被上层的摄像头覆盖,那是返回给上层之后的事了,不是当前层要处理的逻辑。
(2)左右子结点只要有一个无覆盖(只要有一个返回状态为0)
则当前节点返回当前状态1。
因为需要自己去覆盖子结点。
(3)左右子节点只要有一个有摄像头(只要有一个返回状态为1)
则当前节点返回当前状态2。
因为前面已经处理了子结点存在无覆盖的情况,因此剩下的就只有有覆盖和有摄像头的情况了,自己就不需要再去考虑覆盖它们了。而又因为子结点的摄像头能覆盖自己,因此自己的状态就是2
最后还要考虑一种情况!!即最后遍历到root时,root无覆盖。
因为它没有上层了,也就无法返回自己的状态交给上层处理让上层去覆盖.
因此这种情况就要单独处理:就直接在root上安一个摄像头,但是也不用再去改变root的状态,只需要在主函数中调用递归结束后,判断root否为无覆盖,若是则直接res++增加一个摄像头即可
四、代码
int res;//表示使用的最少摄像头数量
int postTraversal(TreeNode* root) {
if(!root) return 2;//空节点返回2,递归结束
int left = postTraversal(root->left);//左
int right = postTraversal(root->right);//右
//中
if(left == 2 && right == 2) return 0;//情况1
else if(left == 0 || right == 0){ //情况2
res++;
return 1;
}
else return 2;//情况3
}
int minCameraCover(TreeNode* root) {
res = 0;
//后序遍历过程中res会不断++,posTraversal(root)返回的只是root的状态数字
if (postTraversal(root)== 0) res++;
return res;
}