法一: 动态规划
一个被支配的节点只会有三种状态
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;
}
};