题目概述
解题思路
我看到这道题的时候,第一思路是贪心。我想着如果要用最少的相机覆盖树的节点,那么:
- 每个叶子节点的父节点都需要放一个相机,这是因为如果把相机放在叶子节点,它覆盖的节点数会更少;同时我们又要覆盖每个叶子节点,所以把相机放在每个叶节点的父节点位置。
- 我们去掉所有已经被覆盖过的节点,对于余下的树,继续采用这个策略,最后检查根节点是否被覆盖,若没被覆盖则放一个相机。
接下来我们思考,如何能根据这个思路,递归地实现树节点的遍历与相机的摆放。也就是说,我们该怎么用一个函数递归地访问节点,然后返回当前节点的状态,再根据节点的状态计算是否需要摆放相机。
首先选用什么方法来遍历树呢,我们采用后序遍历法,因为这样可以保证我们先访问树的叶子节点,后访问父节点,符合我们从下往上贪心地遍历的诉求。
接下来考虑,对于每个节点,我们该把它划为几种状态。实际上主要是两类,一类是已经被覆盖了,一类是还没被覆盖。已经被覆盖的又分为两种,一种是摆放相机的,一种是没有摆放的。这里我们用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])。
接下来考虑如何构建递推公式(从上往下构建):
- 第一种状态是,该节点作为儿子被监控,此时只需考虑左子节点和右子节点用最小的相机监控即可;因为它这里不放相机,所以左右子树只需考虑以自身为节点或者被子节点监控的情况:
- 第二种状态是,该节点自己监控自己,此时左右子树都有三种选择,注意这里不能只考虑dp[L][0]和dp[R][0],所以递推公式是:
- 第三种状态是,该节点被子节点监控,所以只要比较左右子树有一个相机的情况下,哪一种用到的相机更少,递推公式是:
考虑边界情况:如果碰到叶子节点,那么该节点的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]);
}
};