题目:给定一个二叉树,我们在树的节点上安装摄像头。
节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。
计算监控树的所有节点所需的最小摄像头数量。
本题首先要注意到,叶子结点处不放摄像头,在叶子结点的父节点处放摄像头,所用摄像头最少
局部最优:让叶子节点的父节点安摄像头,所用摄像头最少
整体最优:全部摄像头数量所用最少!
首先确定遍历顺序,由于我们从叶子结点开始,所以使用后序遍历(左右中),注意向左孩子和右孩子遍历都用int接收返回值,用来推导中间节点的状态
int traversal(TreeNode* cur) {
// 空节点,该节点有覆盖
if (终止条件) return ;
int left = traversal(cur->left); // 左
int right = traversal(cur->right); // 右
逻辑处理 // 中
return ;
}
接着处理中间节点的状态,根据左右孩子的返回值,由题可知,节点有三种状态,可用三个数字表示:
0:节点没有被摄像头覆盖 1:节点处有摄像头 2:节点处无摄像头但被覆盖
那么遇到空节点该如何判断覆盖情况?由于需要在叶子结点的父节点处放置摄像头,所以空节点处不能有摄像头,不然叶子结点就是有覆盖状态;空节点也不能是无覆盖状态,否则叶子结点处就需要放置摄像头,所以遇到空节点应返回2。
接下来讨论中间节点的情况:
(1)左右孩子都有覆盖,此时中间节点应该是无覆盖状态
(2)左右孩子有一个是无覆盖状态,此时中间节点应该放置摄像头
left == 0 && right == 0 左右节点无覆盖
left == 1 && right == 0 左节点有摄像头,右节点无覆盖
left == 0 && right == 1 左节点有无覆盖,右节点摄像头
left == 0 && right == 2 左节点无覆盖,右节点覆盖
left == 2 && right == 0 左节点覆盖,右节点无覆盖
(3)左右孩子有一个有摄像头,此时中间节点应该是有覆盖状态
left == 1 && right == 2 左节点有摄像头,右节点有覆盖
left == 2 && right == 1 左节点有覆盖,右节点有摄像头
left == 1 && right == 1 左右节点都有摄像头
(注意,左右孩子有一个有摄像头,另一个无覆盖的情况已经讨论过)
(4)头结点没有覆盖
递归结束后判断根节点状态,若无覆盖则摄像头数量加一
代码如下:
// 版本一
class Solution {
private:
int result;
int traversal(TreeNode* cur) {
// 空节点,该节点有覆盖
if (cur == NULL) return 2;
int left = traversal(cur->left); // 左
int right = traversal(cur->right); // 右
// 情况1
// 左右节点都有覆盖
if (left == 2 && right == 2) return 0;
// 情况2
// left == 0 && right == 0 左右节点无覆盖
// left == 1 && right == 0 左节点有摄像头,右节点无覆盖
// left == 0 && right == 1 左节点有无覆盖,右节点摄像头
// left == 0 && right == 2 左节点无覆盖,右节点覆盖
// left == 2 && right == 0 左节点覆盖,右节点无覆盖
if (left == 0 || right == 0) {
result++;
return 1;
}
// 情况3
// left == 1 && right == 2 左节点有摄像头,右节点有覆盖
// left == 2 && right == 1 左节点有覆盖,右节点有摄像头
// left == 1 && right == 1 左右节点都有摄像头
// 其他情况前段代码均已覆盖
if (left == 1 || right == 1) return 2;
// 以上代码我没有使用else,主要是为了把各个分支条件展现出来,这样代码有助于读者理解
// 这个 return -1 逻辑不会走到这里。
return -1;
}
public:
int minCameraCover(TreeNode* root) {
result = 0;
// 情况4
if (traversal(root) == 0) { // root 无覆盖
result++;
}
return result;
}
};