题目来源
题目描述
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) {
}
};
题目解析
难点在于:放相机会影响父节点
思路一
二叉树的问题,我们一般都是从一个点的角度看,思考解决问题时,有几种可能性?需要什么信息?
对于一个节点,它有什么状态,仅仅是放与不放相机吗?还有是否被监控到。
以x为头的树,返回值有三种情况:
- 1)X节点没有放相机,X是被监控的(被孩子监控),而且X下面所有点都被覆盖了。此时,最少需要几个相机
- 2)X节点放了相机,X是被监控的(被自己监控),而且X下面所有点都被覆盖了。此时,最少需要几个相机
- 3)X节点没有放相机,X没有被监控(可以由它的父节点补救),X下面所有的点都被监控了。此时,最少需要几个相机
为什么要求X下发的点都被覆盖?很明显,如果X没有被覆盖,至少有它的父节点能够补救它,但是X下面的点如果没有被覆盖,向上返回爷爷辈的节点就再也无法补救了
class Solution {
// 潜台词:x是头节点,x下方的点都被覆盖的情况下
struct Info{
long uncovered; // X节点没有放相机,X没有被监控(可以由它的父节点补救)。此时,至少需要几个相机
long coveredNoCamera; // X节点没有放相机,X是被监控的(被孩子监控)。此时,x为头的树至少需要几个相机
long coveredHasCamera; //X节点放了相机,X是被监控的(被自己监控)。x为头的树至少需要几个相机
Info(long uncovered, long no, long has): uncovered(uncovered) {
coveredNoCamera = no;
coveredHasCamera = has;
}
};
Info *process(TreeNode* root){
if(root == nullptr){
return new Info(INT32_MAX, 0, INT32_MAX);
}
auto left = process(root->left);
auto right = process(root->right);
// uncovered: X没有被监控-->左右孩子都不得放相机但是它们必须被覆盖的
long uncovered = left->coveredNoCamera + right->coveredHasCamera;
//X节点没有放相机,X是被监控的(被孩子监控)
long coveredNoCamera = std::min(
left->coveredHasCamera + right->coveredHasCamera,
std::min(left->coveredHasCamera + right->coveredNoCamera,
left->coveredNoCamera + right->coveredHasCamera)
);
//X节点放了相机,X是被监控的(被自己监控)
long coveredHasCamera =
std::min(left->uncovered, std::min(left->coveredNoCamera, left->coveredHasCamera))
+ std::min(right->uncovered, std::min(right->coveredNoCamera, right->coveredHasCamera))
+ 1;
return new Info(uncovered, coveredNoCamera, coveredHasCamera);
}
public:
int minCameraCover(TreeNode* root) {
auto info = process(root);
return std::min(std::min( info->coveredHasCamera, info->coveredNoCamera), info->uncovered + 1 );
}
};
贪心优化
(1)确定遍历方式
- 在安排选择摄像头的位置的时候,我们要从底向上进行推导,因为尽量让叶子节点的父节点安装摄像头,这样摄像头的数量才是最少的,这也是本道贪心的原理所在!
- 因此,才有后序遍历的方式
(2)确定状态转移
以X为头,X下面的节点都被监控,X节点有几种状态呢?
- 当前节点有相机
- 当前节点不需要相机(子节点有相机把它给覆盖了)
- 当前节点没有相机并且也没有被子节点给覆盖(那么他只能等他的父节点把它给覆盖了)
(3)实现
class Solution {
enum STATUS{
UNCOVERED,
COVERED_NO_CAMERA,
COVERED_HAS_CAMERA
} ;
// 以x为头,x下方的节点都是被covered,得到的最优解中:
// x是什么状态,在这种状态下,需要至少几个相机
struct Info{
STATUS status;
int cameras;
Info(STATUS status, int cameras) : status(status) , cameras(cameras){
}
};
Info * process(TreeNode *root){
if(root == nullptr){
return new Info(COVERED_NO_CAMERA, 0);
}
auto left = process(root->left);
auto right = process(root->right);
int cameras = left->cameras + right->cameras;
// 当前节点需要相机:左、或右,哪怕有一个没覆盖
if(left->status == UNCOVERED || right->status == UNCOVERED){
return new Info(COVERED_HAS_CAMERA, cameras + 1);
}
// 当前节点不需要相机
if(left->status == COVERED_HAS_CAMERA || right->status == COVERED_HAS_CAMERA){
return new Info(COVERED_NO_CAMERA, cameras);
}
return new Info(UNCOVERED, cameras);
}
public:
int minCameraCover(TreeNode* root) {
auto info = process(root);
return info->cameras + (info->status == UNCOVERED ? 1 : 0);
}
};