968.监控二叉树 树上最小支配集

 法一: 动态规划

一个被支配的节点只会有三种状态

        1.它本身有摄像头

        2.他没有摄像头, 但是它的父节点有摄像头

        3.他没有摄像头, 但是它的子节点有摄像头

我们 dfs(node,state) 记录在node节点时(以node为根的子树),状态为state下的所有最小摄像头

// 本身有摄像头就看左右孩子了

dfs(node,1) = min(dfs(node->left,1),dfs(node->left,2),dfs(node->left,3)) +

                      min(dfs(node->right,1),dfs(node->right,2),dfs(node->right,3)) + 1

// 本身没有摄像头, 左右孩子不能是2

dfs(node,2) = min(dfs(node->left,1),dfs(node->left,3)) +

                      min(dfs(node->left,1),dfs(node->left,3))

// 本身没有摄像头, 要靠子节点保障,同样左右孩子不能是2,而且至少有一个1

dfs(node,3) = min(dfs(node->left,1)+dfs(root->right,3), dfs(node->left,3)+dfs(node->right,1), 

                       dfs(node->left,1)+dfs(root->right,1))

边界条件怎么思考呢:

        tmp=dfs(nullptr,state)  state为1,tmp是INT_MAX/2  state为2,3,tmp是0

        因为state不应该是1,也就是叶子节点的不应该是用nullptr保障 ,所以我们把这种情况要筛掉

        而当他是2,3时,也就是说让nullptr的监控让他的父节点或者子节点保障,但是因为nullptr不需要监控,所以返回0就行

 代码如下:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    //最小支配集 
    int minCameraCover(TreeNode* root) {
        // 首先对于 一个节点,要被覆盖只有三种情况 
        //1. 它本身安装了摄像头                  ->记成蓝色 blue 0
        //2.(他没安装)它的父节点安装了摄像头      ->记成红色 red 1
        //3.(他没安装)它的孩子有一个安装了摄像头  ->记成黄色 yellow 2

        //对于根节点是蓝色 ans = min(l_blue,l_red,l_yellow) + min(r_blue,r_red,r_yellow) + 1
        //对于根节点是红色,子节点不能是红色 ans = min(l_blue,l_yellow) + min(r_blue,r_yellow)
        //对于根节点是黄色,子节点不能是红色 ans = min(l_blue+r_yellow, l_yellow+r_blue, l_blue+r_blue)
        map<pair<TreeNode*,int>,int> memo;
        function<int(TreeNode*,int)> dfs=[&](TreeNode* root,int color)->int{
            if(!root){
                if(color==0) return INT_MAX/2;  //
                else return 0; 
            }
            if(memo.count({root,color})) return memo[{root,color}];
            //null节点不能是蓝色 因为叶子节点不能把希望寄托在null上 把这种情况排除
            //但是null节点可以是红色或者黄色 
            //红色意味着该null也被监视了,虽然没必要,但是也不算错
            //黄色意味着该节点的安全由子节点保证 但是本来这个节点就是null 不用保证其安全
            if(color==0){
                return memo[{root,color}]=min(dfs(root->left,0),min(dfs(root->left,1),dfs(root->left,2))) + min(dfs(root->right,0),min(dfs(root->right,1),dfs(root->right,2))) + 1;
            }else if(color==1){
                return memo[{root,color}]=min(dfs(root->left,0),dfs(root->left,2)) + min(dfs(root->right,0),dfs(root->right,2));
            }else {
                return memo[{root,color}]=min(dfs(root->left,0)+dfs(root->right,2),min(dfs(root->left,2)+dfs(root->right,0),dfs(root->left,0)+dfs(root->right,0)));
            }
        };
        return min(dfs(root,0),dfs(root,2));
    }
};

法二: 贪心

        我们跳出思维定式来看看这个题,怎么 "贪心地" 使得使用最少的节点来支配整个树,显然在一层一层的来看,叶子节点不要放,在叶子节点的父节点放摄像头,然后跳过两个,在这一层放摄像头,然后再跳过两个,在这一层放摄像头,一直这样到根节点. 

        算法的正确可以这样想,叶子节点a一定要被监控,显然放在a的双亲是收益最大的,然后往上找没有监控的节点,用同样的贪心思想

        至于为什么不从根节点往下呢,可以这样想: 叶子节点一定是比根节点要多的,所以应该抓大头

我们把所有节点分成三种情况:

        0. 本身放了摄像头

        1. 本身没有摄像头,但是被子节点监控了

        2. 本身没有摄像头,但是被父节点监控了

这样就是说nullptr往上返回1, (因为显然不会是0,而为了让摄像头最少,又不能是2)

上一层如果收到了下面的两个1,就返回一个2

上一层如果收到了下面的2,就往上返回0 (只要收到一个2)

上一层如果收到了下面的0,就往上返回1 (只要收到一个0)

如果收到了一个0,一个2呢, 显然返回是0, 不如就会有一个节点没有被监控

而最后走到根节点又没有父节点,所有如果根节点返回2, ans是要加1的

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    //最小支配集 
    int minCameraCover(TreeNode* root) { 
        int res=0;
        function<int(TreeNode*)> travel=[&](TreeNode*root)->int{
            if(!root) return 1;
            int left=travel(root->left);
            int right=travel(root->right);
            if(left==1 && right==1) return 2;               //情况1 
            if(left==2 || right==2) { res++; return 0;}     //情况2
            if(left==0 || right==0) return 1;               //情况3  

            return -1;
        };
        if(travel(root)==2) res++;
        return res;
    }
};

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值