leetcode 968.监控二叉树

本文探讨了一种解决二叉树节点监控问题的算法,通过树形动态规划结合贪心策略,实现用最少的摄像头数量完成对所有节点的监控。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

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

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

计算监控树的所有节点所需的最小摄像头数量。
示例 1:
在这里插入图片描述
输入:[0,0,null,0,0]
输出:1
解释:如图所示,一台摄像头足以监控所有节点。
示例 2:
在这里插入图片描述
输入:[0,0,null,0,null,0,null,null,0]
输出:2
解释:需要至少两个摄像头来监视树的所有节点。 上图显示了摄像头放置的有效位置之一。

提示:

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

分析

本题是一个有意思的树形DP+贪心的问题,而且难度不小,很多人题解都很难看懂。如果之前做过没有上司的舞会战略游戏这两道题的话,可能会觉得这题与战略游戏很相似,战略游戏是要观察到所有的边,本题是要监控到所有的点,有异曲同工之妙,但是本题难度更大。

我们先尝试下常规的思路:每个节点可以选择放与不放摄像头,如果必须放,那就放置;否则取放置和不放置的最小值。也就是说,对于某个节点u来说,如果他的父节点还没有被摄像头覆盖到,那么u节点是要放置一个摄像头来监控其父节点的(这里思考下是否真的必须放置?),如果u的父节点被覆盖到了,就分别尝试在u上放置和不放置的两种方案哪个更优。这个思路的漏洞在于,我们没法确定节点u是否必须放摄像头,因为就算它的父节点没有被覆盖到,u也不放置,在u的兄弟节点上放置同样可以监控到u的父节点;但是如果都想着让兄弟节点来放,结果两个孩子都没放摄像头,方案就不合法了,所以这种思路行不通。

回想下线性DP问题,某个位置的状态,要么可以由它左边位置状态转移而来,要么可以由右边状态转移而来,但是状态的转移方向必须是单向的,这是DP的状态无后效性要求的。比如说f[2]由f[1]推出又反过来影响f[1],这显然是不合理的。之所以要说这个,是因为树形DP的问题我们也只能单向进行状态转移,要么只考虑父节点的状态,要么只考虑孩子节点的状态,这样才能有一个合理的状态转移顺序。上面的思路是想着只考虑父节点的状态去转移,结果发现还要依赖于兄弟节点的状态,所以不可行。既然自上而下行不通,不妨尝试下自下而上。如果没有想到自上而下推状态的不合理性,也就不会明白为什么很多人都说这题要自下而上计算了。

进入正题。对于节点u,我们只根据其孩子节点的状态去推出它的状态。这里节点的状态可以分为三种:

  • 0表示未被覆盖到,也就是这个位置没有放置摄像头,其孩子节点也没放置摄像头
  • 1表示被覆盖到,说明其孩子节点放置了摄像头,但是自身没有放置摄像头
  • 2表示该位置放置了摄像头,自然也被覆盖到了

对于节点u而言:

如果其孩子节点中存在未被覆盖到的(状态为0),那么u上必须放置摄像头才能保证孩子被监控到,此时u的状态就是2。(这就是自上而下和自下而上两种方案的区别,一个节点可能有两个孩子,但是只有一个父节点,父节点别无选择,只能放置摄像头)。

如果其孩子节点的状态都是1,也就是被覆盖到了,现在u就不用管其孩子的死活了,只需要考虑自身放不放就行了,这里果断选择不放置。 ** 这是出于贪心考虑,由于u的孩子状态是1,说明没有放置摄像头,孩子节点无法监控到u,如果在u节点放置摄像头,能增加的监控范围是u和u的父节点;如果不在u处放置,在u的父节点处放置摄像头,可能增加的监控范围就是u和u的兄弟节点,u的父节点以及u的祖父节点;既然都是增加一个摄像头,我们自然选择能够增加的范围更大的方案,这样的方案才会是最优的。 ** 这种情况u不放置摄像头,也不能被孩子节点监控到,状态就是0。当然不用担心这个节点不会被监控到,因为只要u有父节点,发现孩子节点不能监控到,一定会放置摄像头的。

如果其孩子节点中存在安装摄像头的,也就是有状态为2的节点,u节点被覆盖到了,自然就不用放置摄像头了,也是贪心的思想,此时u的状态是1,不放置摄像头但是可以被覆盖到。

u最多有两个孩子,其孩子的状态无非是至少存在一个0,都是1,至少存在一个2这三种之一了,状态划分不重不漏。还有个边界问题需要考虑,也就是空节点的状态,NULL的节点状态自然不会是2,因为它不能监控其父节点;也不是状态0,因为它不需要被其父节点监控;所以它的状态就是1,可以被监控到但是没有安装摄像头,这样才不会影响到叶子节点和度为1的节点状态。

最后两个问题。如何自下而上进行状态转移?状态转移的顺序是先左右子树再根节点,所以可以按照后续遍历的顺序进行状态转移。第二个问题,我们可以定义一个dfs函数求出dfs(u),也就是返回对以u为根的子树进行状态转移结束后u的状态,如果最终u的状态是0要怎么处理?我们自下而上转移状态,每个状态只对孩子节点负责,不对父节点负责,所以如果最后的根节点未被覆盖到,增加一个摄像头即可。
本题代码的一个巧妙之处是dfs只返回u的最终状态,在遍历过程中如果出现状态为2的节点就增加计数,这样最终的计数就是摄像头数。
本题的代码很简洁,分析过程很复杂,如果能体会到分析过程中的巧妙,相信对树形DP的理解是大有收获的。

代码

/**
 * Definition for a binary tree node.
 * 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 res = 0;
    int dfs(TreeNode* root) {
        if(!root)   return 1;//能被覆盖到
        int l = dfs(root->left);
        int r = dfs(root->right);
        if(l == 0 || r == 0) {
            res++;
            return 2;//放置摄像头
        }
        if(l == 2 || r == 2)    return 1;//能被覆盖到
        return 0;//不能被覆盖到,l = r = 1
    }
    int minCameraCover(TreeNode* root) {
        if(!dfs(root))   res++;
        return res;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值