leetcode 968. Binary Tree Cameras

这篇博客探讨了一道关于树的节点覆盖问题,如何使用最少的相机覆盖所有节点。作者首先提出了贪心算法的思路,即从叶子节点开始向上放置相机,然后通过后序遍历实现递归。接着,作者介绍了树形DP的解决方案,通过维护每个节点的三种状态来计算最少相机数量。两种方法的时间复杂度均为O(N),空间复杂度也为O(N)。博客提供了详细的解题过程和示例代码。
摘要由CSDN通过智能技术生成

题目概述

解题思路

我看到这道题的时候,第一思路是贪心。我想着如果要用最少的相机覆盖树的节点,那么:

  • 每个叶子节点的父节点都需要放一个相机,这是因为如果把相机放在叶子节点,它覆盖的节点数会更少;同时我们又要覆盖每个叶子节点,所以把相机放在每个叶节点的父节点位置。
  • 我们去掉所有已经被覆盖过的节点,对于余下的树,继续采用这个策略,最后检查根节点是否被覆盖,若没被覆盖则放一个相机。

接下来我们思考,如何能根据这个思路,递归地实现树节点的遍历与相机的摆放。也就是说,我们该怎么用一个函数递归地访问节点,然后返回当前节点的状态,再根据节点的状态计算是否需要摆放相机。

首先选用什么方法来遍历树呢,我们采用后序遍历法,因为这样可以保证我们先访问树的叶子节点,后访问父节点,符合我们从下往上贪心地遍历的诉求。

接下来考虑,对于每个节点,我们该把它划为几种状态。实际上主要是两类,一类是已经被覆盖了,一类是还没被覆盖。已经被覆盖的又分为两种,一种是摆放相机的,一种是没有摆放的。这里我们用0/1/2来表示未被覆盖/摆放相机/已覆盖这三种情况。

然后考虑什么时候遍历终止,对于后续遍历,遍历终止的条件是访问到空的节点;那么对于空的节点,我们该赋值成什么状态比较好呢?

既然空节点不能摆放相机,那么它就不能置为1;由于它不需要检查是否被覆盖,所以不能设成0;这样,我们统一把它设置成2.

接下来我们考虑,在什么情况下,我们需要摆放相机。假设当前节点的左右子节点经过后序遍历,返回的值分别为L_stat和R_stat,现在我们来分析各种情况下函数的返回值。

  • 如果左右节点至少有一个返回的值是0,这时根据上文的贪心思想,当前节点返回的值是1,并且要在此摆放一个相机;
  • 如果左右节点至少有一个返回的值是1,说明当前节点已经被覆盖了,所以返回2;
  • 如果左右节点的返回值都是2,说明当前节点并未被覆盖,所以返回0.

最后思考一下,如果根节点正好碰上第三种情况,这时我们还需要给这个位置摆放一个相机,所以这里需要额外检查一下。

然后学习了官方的题解,发现这题还能用树形DP来做_(:з」∠)_。

考虑每个节点被监控的情况,我们可以按照它被上层/同层/下层监控划分为三种:

第一种状态,被父节点安装的摄像头监控;

第二种状态,被自己安装的摄像头监控;

第三种状态,被子节点安装的摄像头监控。

我们对每个节点,维护三个值——dp[node_i][0]~dp[node_i][2],用于记录在这三种情况下,所需的最少的相机数。那么最后要求的就是max(dp[root][1], dp[root][2])。

接下来考虑如何构建递推公式(从上往下构建):

  1. 第一种状态是,该节点作为儿子被监控,此时只需考虑左子节点和右子节点用最小的相机监控即可;因为它这里不放相机,所以左右子树只需考虑以自身为节点或者被子节点监控的情况:dp[root][0] = min(dp[L][1], dp[L][2]) + min(dp[R][1] + dp[R][2])
  2. 第二种状态是,该节点自己监控自己,此时左右子树都有三种选择,注意这里不能只考虑dp[L][0]和dp[R][0],所以递推公式是:dp[root][1] = 1 + min(dp[L][0], dp[L][1],dp[L][2]) + min(dp[R][0],dp[R][1], dp[R][2])
  3. 第三种状态是,该节点被子节点监控,所以只要比较左右子树有一个相机的情况下,哪一种用到的相机更少,递推公式是:dp[root][2]=min(dp[L][1]+dp[R][1], dp[L][1] + dp[R][2],dp[L][2]+dp[R][1])

考虑边界情况:如果碰到叶子节点,那么该节点的dp[i][0]的值应该是0,因为它必有一个父节点监控之;dp[i][1]的值是1;dp[i][2]的值是1,因为这个节点必定需要一个相机用作监控(虚拟的子节点)。

方法性能

如果树有N个节点,贪心法的时间复杂度是O(N),空间复杂度为O(N)。

树形DP的时间复杂度也是O(N),空间复杂度也是O(N)。

示例代码

/**
 * 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 {
private:
    //We suppose the nodes could be spilt by 3 states: unvisited(0), covered(2) and occupied(1).
    //We use postorder traversal to visit treenodes, because in this way we could visit the leaf nodes earlier.
    //We use greedy method to visit the treenodes: we just need to visit all nodes whose child treenode is leaf node.
    int postTravel(TreeNode* root) 
    {
        if(root == NULL)
            return 2;
        
        int L_stat = postTravel(root->left),
            R_stat = postTravel(root->right),
            multi = L_stat * R_stat;
        
        //check whether the treenode is visited
        if(multi == 4)
            return 0;
        if(multi == 0)
        {
            res++;
            return 1;
        }
        return 2;
        
    }
public:
    int res = 0;
    int minCameraCover(TreeNode* root) 
    {
        if(!postTravel(root))
            res++;
        return res;
    }
};
/**
 * 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:
    unordered_map<TreeNode*, int> dp0, dp1, dp2;
    void postTravel(TreeNode* root)
    {
        if(root == NULL)
            return ;
        
        if(root->left == NULL && root->right == NULL)
        {
            dp0[root] = 0; //since we have to put a camera on the father node to monitor it.
            dp1[root] = 1; 
            dp2[root] = 1; //a camera is required to monitor it.
        }
        else if(root->left == NULL)
        {
            postTravel(root->right);
            dp0[root] = min(dp1[root->right], dp2[root->right]);
            dp1[root] = 1 + min(min(dp0[root->right], dp1[root->right]), dp2[root->right]);
            dp2[root] = dp1[root->right];
        }
        else if(root->right == NULL)
        {
            postTravel(root->left);
            dp0[root] = min(dp1[root->left], dp2[root->left]);
            dp1[root] = 1 + min(min(dp0[root->left], dp1[root->left]), dp2[root->left]);
            dp2[root] = dp1[root->left];            
        }
        else
        {
            postTravel(root->left);
            postTravel(root->right);
            dp0[root] = min(dp1[root->left], dp2[root->left]) + min(dp1[root->right], dp2[root->right]);
            dp1[root] = 1 + min(min(dp0[root->left], dp1[root->left]), dp2[root->left]) 
                          + min(min(dp0[root->right], dp1[root->right]), dp2[root->right]);
            dp2[root] = min(min(dp1[root->left] + dp1[root->right], 
                            dp1[root->left] + dp2[root->right]),
                            dp2[root->left] + dp1[root->right]);
        }
    }
    
    int minCameraCover(TreeNode* root) 
    {
        postTravel(root);
        
        return min(dp1[root], dp2[root]);
    }
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值