leetcode968(监控二叉树:动态规划)

给定一个二叉树,我们在树的节点上安装监控器。
节点上的每个监控器都可以监视其父对象、自身及其直接子对象。
计算监控树的所有节点所需的最小监控器数量。

例:
输入:[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;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值