目录
一,题目描述
英文描述
You are given the root of a binary tree. We install cameras on the tree nodes where each camera at a node can monitor its parent, itself, and its immediate children.
Return the minimum number of cameras needed to monitor all nodes of the tree.
中文描述
给定一个二叉树,我们在树的节点上安装摄像头。
节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。
计算监控树的所有节点所需的最小摄像头数量。
示例与说明
提示:
- 给定树的节点数的范围是
[1, 1000]
。- 每个节点的值都是 0。
来源:力扣(LeetCode)
链接:力扣
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
二,解题思路
参考@笨猪爆破组【「手画图解」从递归优化到树形DP | 监控二叉树】,文章介绍的比较详细,感兴趣的可以戳链接。
算法
状态划分
对于一个非空节点可以分为三种状态:
- 安装摄像头withCam(被自己监控);
- 不安装摄像头(被父节点监控watchByFat);
- 不安装摄像头(被子节点监控watchBySon);
若节点为空,需要注意:
- 空节点不可能放置摄像头(此情况可以将返回值设置为INT_MAX,从而避免错误解被纳入结果考虑范围);
- 空节点被父节点监控或是被子节点监控都返回0即可;
分情况讨论
1,当前节点安装摄像头:(子节点可装可不装,都会被当前节点监控)
- minNum = min(minNum, left["withCam"] + right["watchByFat"]);
- minNum = min(minNum, left["watchByFat"] + right["withCam"]);
- minNum = min(minNum, left["watchByFat"] + right["watchByFat"]);
- minNum = min(minNum, left["withCam"] + right["withCam"]);(此情况可以不用考虑,因为当前节点及左右子节点都安装摄像头的策略,基本不会是最优策略)
2,当前节点不安装摄像头:(子节点不可能被当前节点监控。只能被自己监控,或者被他们的子节点监控)
(当前节点被父节点监控。子节点可装可不装)
- minNum = min(minNum, left["withCam"] + right["withCam"]);
- minNum = min(minNum, left["withCam"] + right["watchBySon"]);
- minNum = min(minNum, left["watchBySon"] + right["withCam"]);
- minNum = min(minNum, left["watchBySon"] + right["watchBySon"]);
(当前节点被子节点监控。子节点至少有一个摄像头)
- minNum = min(minNum, left["withCam"] + right["withCam"]);
- minNum = min(minNum, left["withCam"] + right["watchBySon"]);
- minNum = min(minNum, left["watchBySon"] + right["withCam"]);
至此便考虑到了所有可能成为解的情况,并将计算的结果一步步返回递归的上一层,从而重复利用。
三,AC代码
C++
class Solution {
public:
int minCameraCover(TreeNode* root) {
unordered_map<string, int> ans = getMin(root);
int minNum = 99999999;
minNum = min(minNum, ans["withCam"]);
// minNum = min(minNum, ans["watchByFat"]); // !!!根节点怎么可能被父节点监控
minNum = min(minNum, ans["watchBySon"]);
return minNum;
}
unordered_map<string, int> getMin(TreeNode* root) {
unordered_map<string, int> ans;
if (root == nullptr) {
ans["withCam"] = 99999999;// 空节点不可能有摄像头,所以这里返回一个足够大的值,使得这种情况不可能被纳入最优解考虑范围
ans["watchByFat"] = 0;// 空节点不需要被监控
ans["watchBySon"] = 0;// 空节点不可能被子节点监控
return ans;// !!!必须在这里返回
}
unordered_map<string, int> left = getMin(root->left);
unordered_map<string, int> right = getMin(root->right);
// 当前节点安装摄像头
int minNum = 99999999;
minNum = min(minNum, left["withCam"] + right["watchByFat"]);
minNum = min(minNum, left["watchByFat"] + right["withCam"]);
minNum = min(minNum, left["watchByFat"] + right["watchByFat"]);
int withCam = 1 + minNum;
// 当前节点不安装摄像头
// 被父节点监控
minNum = 99999999;
minNum = min(minNum, left["withCam"] + right["withCam"]);
minNum = min(minNum, left["withCam"] + right["watchBySon"]);
minNum = min(minNum, left["watchBySon"] + right["withCam"]);
minNum = min(minNum, left["watchBySon"] + right["watchBySon"]);
int watchByFat = minNum;
// 被子节点监控(子节点至少有一个摄像头)
minNum = 99999999;
minNum = min(minNum, left["withCam"] + right["withCam"]);
minNum = min(minNum, left["withCam"] + right["watchBySon"]);
minNum = min(minNum, left["watchBySon"] + right["withCam"]);
int watchBySon = minNum;
ans["withCam"] = withCam;
ans["watchByFat"] = watchByFat;
ans["watchBySon"] = watchBySon;
return ans;
}
};
Java
class Solution {
public int minCameraCover(TreeNode root) {
Reult ans = getMin(root);
return Math.min(ans.withCam, ans.watchBySon);
}
public Reult getMin(TreeNode root) {
Reult ans = new Reult();
if (root == null) {
ans.withCam = 99999999;
ans.watchByFat = 0;
ans.watchBySon = 0;
return ans;
}
Reult left = getMin(root.left);
Reult right = getMin(root.right);
// 在该节点放置摄像头
ans.withCam = 99999999;
ans.withCam = Math.min(ans.withCam, left.withCam + right.watchByFat);
ans.withCam = Math.min(ans.withCam, left.watchByFat + right.withCam);
ans.withCam = Math.min(ans.withCam, left.watchByFat + right.watchByFat);
ans.withCam++;
// 该节点不放置摄像头(被父节点监控)
ans.watchByFat = 99999999;
ans.watchByFat = Math.min(ans.watchByFat, left.withCam + right.watchBySon);
ans.watchByFat = Math.min(ans.watchByFat, left.watchBySon + right.withCam);
ans.watchByFat = Math.min(ans.watchByFat, left.withCam + right.withCam);
ans.watchByFat = Math.min(ans.watchByFat, left.watchBySon + right.watchBySon);
// 该节点不放置摄像头(被子节点监控)
ans.watchBySon = 99999999;
ans.watchBySon = Math.min(ans.watchBySon, left.withCam + right.watchBySon);
ans.watchBySon = Math.min(ans.watchBySon, left.watchBySon + right.withCam);
ans.watchBySon = Math.min(ans.watchBySon, left.withCam + right.withCam);
return ans;
}
}
class Reult {
int withCam;
int watchByFat;
int watchBySon;
}
四,解题过程
第一博
这种一般直接想到用递归,只要归纳全部的情况就能得到正确答案,虽然看上去效率很低,但是现实现出来再说
class Solution {
public int minCameraCover(TreeNode root) {
return Math.min(getAns(root, 0), getAns(root, 1));
}
// flag表示当前节点是否安装摄像头
public int getAns(TreeNode root, int flag) {
if (root == null) return 0;
int minNum = 99999999;
if (flag == 1) {
minNum = Math.min(minNum, getAns(root.left, 1) + getAns(root.right, 1));
minNum = Math.min(minNum, getAns(root.left, 1) + getAns(root.right, 0));
minNum = Math.min(minNum, getAns(root.left, 0) + getAns(root.right, 1));
minNum = Math.min(minNum, getAns(root.left, 0) + getAns(root.right, 0));
} else {
if (root.left != null && root.right != null) {
minNum = Math.min(minNum, getAns(root.left, 1) + getAns(root.right, 1));
minNum = Math.min(minNum, getAns(root.left, 1) + getAns(root.right, 0));
minNum = Math.min(minNum, getAns(root.left, 0) + getAns(root.right, 1));
} else if (root.left == null && root.right == null) {
return flag;
} else if (root.left == null) {
minNum = Math.min(minNum, getAns(root.left, 0) + getAns(root.right, 1));
} else if (root.right == null) {
minNum = Math.min(minNum, getAns(root.left, 1) + getAns(root.right, 0));
}
}
System.out.println(minNum + flag);
return minNum + flag;
}
}
显然上面的方法没有考虑到父节点的状态,因此没有包含所有的状态,最后只过了121/171
第二搏
参考了大佬的解题思路,发现是自己考虑的情况不够全面 ,比如只考虑当前节点放不放摄像头,未考虑父节点或子节点对当前节点监控状态的影响,这样就无法完全保证覆盖关系,无法判断连续两个节点不放置摄像头的情况。
因此需要引入当前节点是否被监视的参数。
class Solution {
public int minCameraCover(TreeNode root) {
return Math.min(getNum(root, true, true), getNum(root, false, false));
}
// hasCame: 该节点是否有摄像头,isWatched: 是否被自己或父节点监控
public int getNum(TreeNode root,boolean hasCame, boolean isWatched) {
if (root == null) {
if (hasCame) return 99999999;
else return 0;
}
int minNum = 99999999;
// 该节点有摄像头(子结点可装可不装,舍弃子结点全部安装的情况,因为该情况不可能作为最终解)
if (hasCame) {
minNum = Math.min(minNum, getNum(root.left, true, true) + getNum(root.right, false, true) + 1);
minNum = Math.min(minNum, getNum(root.left, false, true) + getNum(root.right, true, true) + 1);
minNum = Math.min(minNum, getNum(root.left, false, true) + getNum(root.right, false, true) + 1);
}
// 该节点无摄像头
else {
// 父节点有摄像头,被父节点监控(子结点可装可不装)
if (isWatched) {
minNum = Math.min(minNum, getNum(root.left, true, true) + getNum(root.right, true, true));
minNum = Math.min(minNum, getNum(root.left, false, false) + getNum(root.right, true, true));
minNum = Math.min(minNum, getNum(root.left, true, true) + getNum(root.right, false, false));
minNum = Math.min(minNum, getNum(root.left, false, false) + getNum(root.right, false, false));
}
// 父节点无摄像头,被子结点监控(至少有一个子结点要装摄像头)
else {
minNum = Math.min(minNum, getNum(root.left, true, true) + getNum(root.right, false, false));
minNum = Math.min(minNum, getNum(root.left, false, false) + getNum(root.right, true, true));
minNum = Math.min(minNum, getNum(root.left, true, true) + getNum(root.right, true, true));
}
}
return minNum;
}
}
显然,后面的测试用例都是超时了。
第三博
可以从上面的算法中看出,为保证正确性,多次对同一颗子树进行求解,导致计算重复,如果能够在一次递归中将各种情况的计算结果一次性返回,并且重复利用就可以提高效率。这便是树形DP
解题过程用的是map,这样看起来更直观一点,但是开销比较大,想提高效率可以用数组代替