D37第八章 贪心算法 part06 ● 738.单调递增的数字 ● 968.监控二叉树 ● 总结

第八章 贪心算法 part06

  • 738.单调递增的数字
  • 给定一个非负整数N,找出小于或等于N的最大的整数,同时这个整数需要满足其各个位数上的数字是单调递增。(当且仅当每个相邻位数上的数字x和y满足x<=y,我们称这个整数是单调递增的)
  • 题目要求小于等于N的最大单调递增的整数,那么拿一个两位数的数字来举例。例如:98.一旦出现非递增的情况,这样这个整数就是89.即小于98的最大的单调递增整数。此时是从前向后遍历还是从后向前遍历呢。从前向后遍历的话,遇到strnum[i-1]>strnum[i]的情况,让strnum[i-1]减一,但此时如果减一,可能又小于strnum[i-2].这么说有点抽象,举个例子:332会变成329,此时2又小于前一位了,真正的结果应该是299.那么从后向前遍历,就可以重复利用上次比较得出的结果了,从后往前遍历332的数值变化为332》329》299确定了遍历顺序后,那么此时局部最优就可以退出全局,找不出反例。试试贪心。
  • 本题只要想清楚个例,例如98,一旦出现非单调递增,首先想让左边减一,右边赋值9,这样这个整数就是89,就可以很自然想到对应的贪心解法了。想到了贪心,还要考虑遍历顺序,只有从后往前遍历才能重复利用上次比较的结果,最后实现代码的时候,也需要一些技巧,例如用一个flag来标记从哪里开始赋值9
  • class Solution {
        public int monotoneIncreasingDigits(int n) {
            String s = String.valueOf(n);
            char[] chars = s.toCharArray();
            int start = s.length();
            for (int i = s.length() - 2; i >= 0; i--) {
                if (chars[i] > chars[i + 1]) {
                    chars[i]--;
                    start = i+1;
                }
            }
            for (int i = start; i < s.length(); i++) {
                chars[i] = '9';
            }
            return Integer.parseInt(String.valueOf(chars));
        }
    }

  • 968.监控二叉树
  • 给定一个二叉树,我们在树的结点上安装摄像头。结点上的每个摄像头都可以监视其父对象,自身及其直接子对象。计算监控树的所有结点所需要的最小摄像头数量。这道题首先要想,如何放置,才能让摄像头最小呢?从题目中实例,其实可以得到启发,我们发现题目示例中的摄像头都没有放在叶子节点上,这是一个很重要的线索,摄像头可以覆盖上中下三层,如果把摄像头放在叶子结点上,就浪费一层的覆盖。所以把摄像头放在叶子结点的父节点位置,才能充分利用摄像头的覆盖面积。那么有同学就要问了,为什么不从头结点开始看起呢,为啥要从叶子结点看呢?因为头结点放不放摄像头也就省下了一个摄像头,叶子结点放不放摄像头省下的摄像头数量是指数级别的。所以我们要从下往上看,局部最优:让叶子结点的父节点安摄像头,所用摄像头最少,整体最优f:全部摄像头数量所用最少。局部最优推出全局最优,找不出反例,那么就按照 贪心来。此时,大体思路就是从低到上,先给叶子结点父节点放个摄像头,然后隔两个结点放一个摄像头,直到二叉树头结点。
  • 此时这道题目还有两个难点:
  • 二叉树的遍历
  • 如何隔两个结点放一个摄像头
  • 确定遍历顺序,在二叉树中如何从低向上推导呢。可以使用后续遍历也就是左右中的顺序,这样就可以在回溯的过程中从下到上进行推导了。后续遍历代码如下:
  • 注意在以上代码中我们取了做孩子的返回值,右孩子的返回值,即left和right。以后推导中间结点的状态。
  • 如何隔两个结点放一个摄像头,此时需要转移状态的公式,大家不要和动态的状态转移公式混在一起,本题状态转移没有择优的过程,就是单纯的状态转移
  • 来看看这几个状态应该如何转移,先来看看每个结点可能有几种状态:
  • 该结点无覆盖  本节点有摄像头  本节点有覆盖
  • 我们用三个数字表示:0无覆盖 1有摄像头 2有覆盖
  • 因为在遍历树的过程中,就会遇到空节点,那么问题来了,空节点究竟是哪一种状态呢,空节点表示无覆盖 有摄像头 还是有覆盖呢
  • 回归本质,为了让摄像头数量最少,我们尽量让叶子结点的父节点安装摄像头,这样才能摄像头的数量最少。那么空节点不能是无覆盖的状态,这样叶子结点就要放摄像头了,空节点也不能是有摄像头的状态,。所以空节点的装填只能是有覆盖,这样就可以在叶子结点的父亲 放摄像头了。接下来就是递推关系。那么递归的终止条件应该是遇到了空节点,此时应该返回2,
  • 单层逻辑:情况1f:左右节点有覆盖,那么此时中间结点应该就是无覆盖状态了。
  • 情况2:左右结点至少有1个无覆盖的情况,如果是一下情况,则中间结点应该放摄像头:这个不难理解,毕竟有一个孩子没有覆盖,父节点就应放摄像头,此时摄像头的数量要加1,并且要return1,代表中间结点放摄像头。
  • 情况3:左右结点至少有1个摄像头,如果是一下情况,其实就是左右孩子结点有一个有摄像头了,那么其父节点就应该是2,覆盖状态,
  • 情况4:头结点没有覆盖,以上都处理完了,递归结束后,可能头结点还有一个无覆盖的情况,所以递归结束之后,还要判断根结点,如果没有覆盖,result加加,
  • class Solution {
        int  res=0;
        public int minCameraCover(TreeNode root) {
            // 对根节点的状态做检验,防止根节点是无覆盖状态 .
            if(minCame(root)==0){
                res++;
            }
            return res;
        }
        /**
         节点的状态值:
           0 表示无覆盖
           1 表示 有摄像头
           2 表示有覆盖
        后序遍历,根据左右节点的情况,来判读 自己的状态
         */
        public int minCame(TreeNode root){
            if(root==null){
                // 空节点默认为 有覆盖状态,避免在叶子节点上放摄像头
                return 2;
            }
            int left=minCame(root.left);
            int  right=minCame(root.right);
    
            // 如果左右节点都覆盖了的话, 那么本节点的状态就应该是无覆盖,没有摄像头
            if(left==2&&right==2){
                //(2,2)
                return 0;
            }else if(left==0||right==0){
                // 左右节点都是无覆盖状态,那 根节点此时应该放一个摄像头
                // (0,0) (0,1) (0,2) (1,0) (2,0)
                // 状态值为 1 摄像头数 ++;
                res++;
                return 1;
            }else{
                // 左右节点的 状态为 (1,1) (1,2) (2,1) 也就是左右节点至少存在 1个摄像头,
                // 那么本节点就是处于被覆盖状态
                return 2;
            }
        }
    }

  • 总结
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值