给定一个二叉树,我们在树的节点上安装监控器。
节点上的每个监控器都可以监视其父对象、自身及其直接子对象。
计算监控树的所有节点所需的最小监控器数量。
例:
输入:[0,0,null,0,0]
输出:1
输入:[0,0,null,0,null,0,null,null,0]
输出:2
题解:动态规划,对于树中的每一个节点,其一共有两种状态,要么在该节点放置监控器,要么在该结点不放置监控器,围绕这一点设计动态规划的状态转移方程的话,这道题的思路就会清晰很多。
~
~
~
~
~
~
~
~
~
用到动态规划,我们一般是用数组来存储状态,但考虑到这道题是建立在二叉树的基础之上的,难以用数组来存储二叉树每一个节点的状态,所以这里需要用到哈希表,以“节点——状态值”的形式来进行存储。
动态规划方程(对于递归遍历到的每一个节点):
我们需要用到三个哈希表:
哈希表choose——当前节点如果放置监控器,以当前节点作为根结点的子树所需要的最少监控器数量。
哈希表notChoose——当前节点如果不放置监控器,以当前节点作为根结点的子树所需要的最少监控器数量。
哈希表getMin——以当前节点作为根结点的子树所需要的最少监控器数量。
(1)如果当前节点放置监控器,则其左右孩子都在监控范围内,其左右孩子无论是否选择放置监控器均可,我们只需要让左右子树所放置的监控器最少即可。需要注意的是,如果在该节点的左孩子或右孩子上选择不放监控器,则我们需要让不放监控器的节点的子树被监控器覆盖,同时所需的监控器数量最少
(2)如果当前节点不放置监控器,则其左右孩子中至少要有一个孩子选择放置监控器,我们需要让左右子树在三种情况下(左右均放,左放右不放,左不放右放)所放置的监控器之和最小。(如果左右节点其中一个为叶子节点,则为叶子节点的节点一定要放置,这一点需要分类讨论,如果左右节点均为null,我们直接让该叶子节点所对应的最少监控器树为0)
综上,最后分析得到的动态规划方程:
class Solution {
/**
* {@code Map<TreeNode,Integer>choose}用于记录以原二叉树中每个子树的根结点在
* 选择放置监控器的情况下,该子树所需要的最小摄像头数量
* {@code Map<TreeNode,Integer>notChoose}用于记录以原二叉树每个子树的根结点在
* 选择不放置监控器的情况下,该子树所需要的最小摄像头数量
* {@code Map<TreeNode,Integer>getMin}用于记录原二叉树的每个子树所需要的最小摄像头
* 数量
*/
private final Map<TreeNode,Integer>choose=new HashMap<>(){{put(null,0);}};
private final Map<TreeNode,Integer>notChoose=new HashMap<>(){{put(null,0);}};
private final Map<TreeNode,Integer>getMin=new HashMap<>(){{put(null,0);}};
public int minCameraCover(TreeNode root) {
DFS(root);
return getMin.get(root);
}
@SuppressWarnings("all")
private void DFS(TreeNode node){
if(node==null)
return;
/*
* 如果当前结点是叶子节点,
* 则需要一个监控器实现覆盖
*/
if(node.left==null&&node.right==null){
choose.put(node,1);
notChoose.put(node,0);
getMin.put(node,1);
return;
}
/*
* 递归,由下而上计算当前二叉树的每棵子树
* 所需要的最少监控器
*/
if(!choose.containsKey(node.left))
DFS(node.left);
if(!choose.containsKey(node.right))
DFS(node.right);
int leftChoose=choose.get(node.left);
int rightChoose=choose.get(node.right);
int rightNotChoose=0;
int leftNotChoose=0;
//如果在该结点选择放置监控器
if(node.left!=null)
leftNotChoose= getMin.get(node.left.left)+getMin.get(node.left.right);
else
leftNotChoose=0;
if(node.right!=null)
rightNotChoose=getMin.get(node.right.left)+getMin.get(node.right.right);
else
rightNotChoose=0;
int leftMin=Math.min(leftChoose,leftNotChoose);
int rightMin=Math.min(rightChoose,rightNotChoose);
choose.put(node,leftMin+rightMin+1);
//如果在该结点不选择放置监控器
leftChoose=choose.get(node.left);
rightChoose=choose.get(node.right);
leftNotChoose=notChoose.get(node.left);
rightNotChoose=notChoose.get(node.right);
int min;
if(node.right==null&&node.left!=null){
min=leftChoose;
}
else if(node.left==null&&node.right!=null) {
min=rightChoose;
}
else {
if(isLeaf(node.left)&&isLeaf(node.right))
min = leftChoose + rightChoose;
else if(!isLeaf(node.right)&&isLeaf(node.left)){
min=leftChoose+Math.min(rightChoose,rightNotChoose);
}
else if(!isLeaf(node.left)&&isLeaf(node.right)){
min=rightChoose+Math.min(leftChoose,leftNotChoose);
}
else{
int temp=Math.min(rightChoose+leftNotChoose,rightNotChoose+leftChoose);
min=Math.min(temp,leftChoose+rightChoose);
}
}
notChoose.put(node,min);
getMin.put(node,Math.min(choose.get(node),notChoose.get(node)));
}
/**
* 判断节点是否是其属于的二叉树的叶子节点
* @param node 二叉树结点
* @return 如果是叶子节点,则返回{@code true},否则返回{@code false}
*/
private boolean isLeaf(TreeNode node){
return node.left==null&&node.right==null;
}
}