法一: 动态规划
一个被支配的节点只会有三种状态
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;} //情况2if(left==0 || right==0) return 1; //情况3 return -1;};if(travel(root)==2) res++;return res;}
};