0968-监控二叉树

Raphael Liu Lv10

给定一个二叉树,我们在树的节点上安装摄像头。

节点上的每个摄影头都可以监视 其父对象、自身及其直接子对象。

计算监控树的所有节点所需的最小摄像头数量。

示例 1:

![](https://assets.leetcode-cn.com/aliyun-lc-
upload/uploads/2018/12/29/bst_cameras_01.png)

**输入:** [0,0,null,0,0]
**输出:** 1
**解释:** 如图所示,一台摄像头足以监控所有节点。

示例 2:

![](https://assets.leetcode-cn.com/aliyun-lc-
upload/uploads/2018/12/29/bst_cameras_02.png)

**输入:** [0,0,null,0,null,0,null,null,0]
**输出:** 2
**解释:** 需要至少两个摄像头来监视树的所有节点。 上图显示了摄像头放置的有效位置之一。

提示:

  1. 给定树的节点数的范围是 [1, 1000]
  2. 每个节点的值都是 0。

方法一:动态规划

思路与算法

本题以二叉树为背景,不难想到用递归的方式求解。本题的难度在于如何从左、右子树的状态,推导出父节点的状态。

为了表述方便,我们约定:如果某棵树的所有节点都被监控,则称该树被「覆盖」。

假设当前节点为 root,其左右孩子为 left}, \textit{right。如果要覆盖以 root 为根的树,有两种情况:

  • 若在 root 处安放摄像头,则孩子 left}, \textit{right 一定也会被监控到。此时,只需要保证 left 的两棵子树被覆盖,同时保证 right 的两棵子树也被覆盖即可。
  • 否则, 如果 root 处不安放摄像头,则除了覆盖 root 的两棵子树之外,孩子 left}, \textit{right 之一必须要安装摄像头,从而保证 root 会被监控到。

根据上面的讨论,能够分析出,对于每个节点 root ,需要维护三种类型的状态:

  • 状态 a:root 必须放置摄像头的情况下,覆盖整棵树需要的摄像头数目。
  • 状态 b:覆盖整棵树需要的摄像头数目,无论 root 是否放置摄像头。
  • 状态 c:覆盖两棵子树需要的摄像头数目,无论节点 root 本身是否被监控到。

根据它们的定义,一定有 a \geq b \geq c。

对于节点 root 而言,设其左右孩子 left}, \textit{right 对应的状态变量分别为 (l_a,l_b,l_c) 以及 (r_a,r_b,r_c)。根据一开始的讨论,我们已经得到了求解 a,b 的过程:

  • a = l_c + r_c + 1
  • b = \min(a, \min(l_a + r_b, r_a + l_b))

对于 c 而言,要保证两棵子树被完全覆盖,要么 root 处放置一个摄像头,需要的摄像头数目为 a;要么 root 处不放置摄像头,此时两棵子树分别保证自己被覆盖,需要的摄像头数目为 l_b + r_b。

需要额外注意的是,对于 root 而言,如果其某个孩子为空,则不能通过在该孩子处放置摄像头的方式,监控到当前节点。因此,该孩子对应的变量 a 应当返回一个大整数,用于标识不可能的情形。

最终,根节点的状态变量 b 即为要求出的答案。

代码

[sol1-C++]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct Status {
int a, b, c;
};

class Solution {
public:
Status dfs(TreeNode* root) {
if (!root) {
return {INT_MAX / 2, 0, 0};
}
auto [la, lb, lc] = dfs(root->left);
auto [ra, rb, rc] = dfs(root->right);
int a = lc + rc + 1;
int b = min(a, min(la + rb, ra + lb));
int c = min(a, lb + rb);
return {a, b, c};
}

int minCameraCover(TreeNode* root) {
auto [a, b, c] = dfs(root);
return b;
}
};
[sol1-Java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public int minCameraCover(TreeNode root) {
int[] array = dfs(root);
return array[1];
}

public int[] dfs(TreeNode root) {
if (root == null) {
return new int[]{Integer.MAX_VALUE / 2, 0, 0};
}
int[] leftArray = dfs(root.left);
int[] rightArray = dfs(root.right);
int[] array = new int[3];
array[0] = leftArray[2] + rightArray[2] + 1;
array[1] = Math.min(array[0], Math.min(leftArray[0] + rightArray[1], rightArray[0] + leftArray[1]));
array[2] = Math.min(array[0], leftArray[1] + rightArray[1]);
return array;
}
}
[sol1-Golang]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const inf = math.MaxInt32 / 2 // 或 math.MaxInt64 / 2

func minCameraCover(root *TreeNode) int {
var dfs func(*TreeNode) (a, b, c int)
dfs = func(node *TreeNode) (a, b, c int) {
if node == nil {
return inf, 0, 0
}
la, lb, lc := dfs(node.Left)
ra, rb, rc := dfs(node.Right)
a = lc + rc + 1
b = min(a, min(la+rb, ra+lb))
c = min(a, lb+rb)
return
}
_, ans, _ := dfs(root)
return ans
}

func min(a, b int) int {
if a < b {
return a
}
return b
}
[sol1-JavaScript]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var minCameraCover = function(root) {
const dfs = (root) => {
if (!root) {
return [Math.floor(Number.MAX_SAFE_INTEGER / 2), 0, 0];
}
const [la, lb, lc] = dfs(root.left);
const [ra, rb, rc] = dfs(root.right);
const a = lc + rc + 1;
const b = Math.min(a, Math.min(la + rb, ra + lb));
const c = Math.min(a, lb + rb);
return [a, b, c];
}

return dfs(root)[1];
};
[sol1-Python3]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution:
def minCameraCover(self, root: TreeNode) -> int:
def dfs(root: TreeNode) -> List[int]:
if not root:
return [float("inf"), 0, 0]

la, lb, lc = dfs(root.left)
ra, rb, rc = dfs(root.right)
a = lc + rc + 1
b = min(a, la + rb, ra + lb)
c = min(a, lb + rb)
return [a, b, c]

a, b, c = dfs(root)
return b
[sol1-C]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct Status {
int a, b, c;
};

struct Status dfs(struct TreeNode* root) {
if (!root) {
return (struct Status){INT_MAX / 2, 0, 0};
}
struct Status l = dfs(root->left);
struct Status r = dfs(root->right);
int a = l.c + r.c + 1;
int b = fmin(a, fmin(l.a + r.b, r.a + l.b));
int c = fmin(a, l.b + r.b);
return (struct Status){a, b, c};
}

int minCameraCover(struct TreeNode* root) {
struct Status ret = dfs(root);
return ret.b;
}

复杂度分析

  • 时间复杂度:O(N),其中 N 为树中节点的数量。对于每个节点,我们在常数时间内计算出 a,b,c 三个状态变量。

  • 空间复杂度:O(N)。每次递归调用时,我们需要开辟常数大小的空间存储状态变量的取值,而递归栈的深度等于树的深度,即 O(N)。

 Comments
On this page
0968-监控二叉树