mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-24 20:00:37 +00:00
8.0 KiB
8.0 KiB
leetcode 968. 监控二叉树
题目描述
给定一个二叉树,我们在树的节点上安装监控。
节点上的每个摄影头都可以监视其父对象、自身及其直接子对象
计算监控树的所有节点所需的最小监控数量。
2个例子:
case1:
1
|
2 监控
/ \
3 4
Case2:
1
/
2 监控
/
3
/
4 监控
\
5
分析
-
一颗监控可以覆盖:当前节点、当前节点的父节点、当前节点的所有子节点3层
-
本题目要求使用最小数量的监控解决问题。那么也就是贪心思维的体现,那么问题来了,什么策略才可以使用最小数量的监控?
- 一棵二叉树中,叶子节点数量肯定是最多的。所以要想最小数量安装监控,优先选择非叶子节点上安装监控。这也是本题中贪心思维的体现。
- 如果最后一层叶子节点不安装监控,那么肯定是叶子节点的父节点安装监控,同理叶子节点的父节点的父节点,也会被监控覆盖到,所以叶子节点的父节点的父节点不用安装监控。于是 叶子节点的父节点的父节点的父节点就必须安装监控。
-
对于二叉树一定是采用递归法或者迭代法解决,本题选择递归法。
-
因为要根据叶子节点的状态来反推父节点的状态,所以采用后续遍历。
-
另外需要根据左右子树的返回值来判断,所以递归函数需要返回值
-
为了方便定义3个状态。
- 未设置监控 NotCovered = 0
- 被监控覆盖 IsCoverd = 1
- 设置监控 SetCamera = 2
-
如何定义递归函数?
- 返回值就是数字,存在3种情况,也就是上面定义的3种状态
- 递归函数的终止条件是什么?遇到叶子节点的左右空子树的情况下,该选用什么状态?思考下:共3种情况
- 空节点选用 “未覆盖 NotCovered”? ❌ 问题 :叶子节点必须安装监控来覆盖空节点 如果空节点是 NotCovered,那么空节点的父节点,也就是叶子节点,必须设置监控,才可以保证叶子节点的左右子节点才可以被监控覆盖到。 ❌ 结果:摄像头数量过多,不符合最小化原则 这和题目要求的最小监控数量不契合
- 空节点选用 “安装监控 SetCamera”? ❌ 问题:叶子节点自动变为被覆盖状态 (IsCovered = 1) 如果空节点是 SetCamera,那么空节点的父节点,也就是叶子节点,状态一定是被覆盖 IsCoverd,如果叶子节点是 IsCoverd, ❌ 结果:叶子节点的父节点不需要安装监控,破坏了"叶子节点的父节点安装监控"的最优策略 反推叶子节点的父节点就不需要安装监控了(因为监控必须间隔设置,覆盖 - 监控 - 覆盖 - 监控 这样的形式),那这个情况也和题目的预设条件不满足。
- 所以空节点应该选用 “被监控覆盖 IsCoverd” 这个状态 ✅ 正确:叶子节点为未覆盖状态 (NotCovered = 0) 空节点被监控覆盖 IsCoverd,那么空节点的父节点,也就是叶子节点就不需要设置监控, ✅ 结果:叶子节点的父节点必须安装监控,符合贪心策略 也就是未设置监控 NotCovered = 0,那么叶子节点的父节点才需要设置监控 SetCamera
状态转换情况
说明:
- 未设置监控 NotCovered = 0
- 被监控覆盖 IsCoverd = 1
- 设置监控 SetCamera = 2
| leftChild | rightChild | root | count=0 | 说明 |
|---|---|---|---|---|
| 1 | 1 | 0 | +0 | 空节点(叶子节点的子节点)必须同时处于被覆盖状态 |
| 0 | 0 | 2 | +1 | 普通节点不管 left、right 只要有1个处于未覆盖状态,那么父节点一定要设置监控才可以“罩着”下面的子节点 |
| 0 | 1 | 2 | +1 | |
| 1 | 0 | 2 | +1 | |
| 2 | 0 | 2 | +1 | |
| 0 | 2 | 2 | +1 | |
| 2 | 2 | 1 | +0 | 其他情况,父节点都是处于被监控覆盖的状态,不需要增加监控 |
| 2 | 1 | 0 | +0 | |
| 1 | 2 | 1 | +0 | |
| 2 | 1 | 1 | +0 |
贪心思想
本题目贪心体现在(监控数量 count = 0):
- 空节点(叶子节点的子节点):处于被监控状态(IsCovered 状态),没有安装监控。count 不变
- 叶子节点:不设置监控,处于 NotCovered 状态。需要父节点罩着。count 不变
- 叶子节点的父节点:安装监控,处于 SetCamera 状态,count++
- 叶子节点的爷爷节点:处于被监控状态(IsCovered 状态),没有安装监控。count 不变
- ♻️ 循环往复
代码实现(JS 为例)
/**
* @param {TreeNode} root
* @return {number}
*/
var minCameraCover = function(root) {
let count = 0
const Mode_NotCovered = 0 // 未设置监控
const Mode_IsCovered = 1 // 被监控覆盖
const Mode_SetCamera = 2 // 设置监控
const traverse = (node) => {
// 空节点视为已覆盖(推到过程见上面注释部分)
if (node === null) return Mode_IsCovered
// 后序遍历
let left = traverse(node.left)
let right = traverse(node.right)
// 如果左右孩子有一个未被覆盖,当前节点需要安装摄像头
if (left === Mode_NotCovered || right === Mode_NotCovered) {
count++
return Mode_SetCamera
}
// 如果左孩子和右孩子都是覆盖状态,那么父节点处于非覆盖状态
if (left === Mode_IsCovered && right === Mode_IsCovered) {
return Mode_NotCovered
}
// 如果左孩子或者右孩子是设置监控状态,那么父节点处于监控覆盖状态
if (left === Mode_SetCamera || right === Mode_SetCamera) {
return Mode_IsCovered
}
return Mode_NotCovered
}
let rootResult = traverse(root)
// 检查根节点状态,如果未被覆盖则需要增加一个摄像头
if (rootResult === Mode_NotCovered) count++
return count
};
提交后发现空间复杂度一般,去掉定义的状态和更加清楚的 if 分支,代码如下
var minCameraCover = function(root) {
let count = 0
const traverse = (node) => {
// 空节点视为已覆盖(推到过程见上面注释部分)
if (node === null) return 1
// 后序遍历
let left = traverse(node.left)
let right = traverse(node.right)
// 如果左右孩子有一个未被覆盖,当前节点需要安装摄像头
if (left === 0 || right === 0) {
count++
return 2
}
// 如果左孩子或者右孩子是设置监控状态,那么父节点处于监控覆盖状态
if (left === 2 || right === 2) {
return 1
}
return 0
}
let rootResult = traverse(root)
// 检查根节点状态,如果未被覆盖则需要增加一个摄像头
if (rootResult === 0) count++
return count
};