随想录Day37--贪心算法 : 738.单调递增的数字 , 968.监控二叉树 (可以跳过),总结

单调递增的数字 这道题有两种思路,一种是从前往后遍历,直到找到没有递增的那个数,这时这个数减1,后面剩下的数全部变成9.。但是这个思路有个问题,就是你减1后,会不会使得1前面的数就不满足递增的条件了。所以还要去判断这一串数字中,是否有相等,因为相等的减1后才小于前面的数,如果不相等,即使减1,最多也就是和前面相等。

另一种思路就是从后往前遍历,如果发现后面的比前面的小,那就把去前面的减1,后面设置一个标致位,准备全部变成9的。(看代码会更清晰)

思路:

题目要求小于等于N的最大单调递增的整数,那么拿一个两位的数字来举例。

例如:98,一旦出现strNum[i - 1] > strNum[i]的情况(非单调递增),首先想让strNum[i - 1]--,然后strNum[i]给为9,这样这个整数就是89,即小于98的最大的单调递增整数。

这一点如果想清楚了,这道题就好办了。

此时是从前向后遍历还是从后向前遍历呢?

从前向后遍历的话,遇到strNum[i - 1] > strNum[i]的情况,让strNum[i - 1]减一,但此时如果strNum[i - 1]减一了,可能又小于strNum[i - 2]。

这么说有点抽象,举个例子,数字:332,从前向后遍历的话,那么就把变成了329,此时2又小于了第一位的3了,真正的结果应该是299。

那么从后向前遍历,就可以重复利用上次比较得出的结果了,从后向前遍历332的数值变化为:332 -> 329 -> 299

本题只要想清楚个例,例如98,一旦出现strNum[i - 1] > strNum[i]的情况(非单调递增),首先想让strNum[i - 1]减一,strNum[i]赋值9,这样这个整数就是89。就可以很自然想到对应的贪心解法了。

想到了贪心,还要考虑遍历顺序,只有从后向前遍历才能重复利用上次比较的结果。

最后代码实现的时候,也需要一些技巧,例如用一个flag来标记从哪里开始赋值9。

968.监控二叉树 直接看解答,主要还是条件的判断和思路。

总结:

贪心简单题


以下三道题目就是简单题,大家会发现贪心感觉就是常识。是的,如下三道题目,就是靠常识,但我都具体分析了局部最优是什么,全局最优是什么,贪心也要贪的有理有据!

  • 贪心算法:分发饼干(opens new window)

  • 贪心算法:K次取反后最大化的数组和(opens new window)

  • 贪心算法:柠檬水找零(opens new window)

#贪心中等题


贪心中等题,靠常识可能就有点想不出来了。开始初现贪心算法的难度与巧妙之处。

  • 贪心算法:摆动序列(opens new window)

  • 贪心算法:单调递增的数字(opens new window)

#贪心解决股票问题

大家都知道股票系列问题是动规的专长,其实用贪心也可以解决,而且还不止就这两道题目,但这两道比较典型,我就拿来单独说一说

  • 贪心算法:买卖股票的最佳时机II(opens new window)

  • 贪心算法:买卖股票的最佳时机含手续费 (opens new window)本题使用贪心算法比较绕,建议后面学习动态规划章节的时候,理解动规就好

#两个维度权衡问题

在出现两个维度相互影响的情况时,两边一起考虑一定会顾此失彼,要先确定一个维度,再确定另一个一个维度。

  • 贪心算法:分发糖果(opens new window)

  • 贪心算法:根据身高重建队列(opens new window)

在讲解本题的过程中,还强调了编程语言的重要性,模拟插队的时候,使用C++中的list(链表)替代了vector(动态数组),效率会高很多。

所以在贪心算法:根据身高重建队列(续集) (opens new window)详细讲解了,为什么用list(链表)更快!

大家也要掌握自己所用的编程语言,理解其内部实现机制,这样才能写出高效的算法!

#贪心难题


这里的题目如果没有接触过,其实是很难想到的,甚至接触过,也一时想不出来,所以题目不要做一遍,要多练!

#贪心解决区间问题

关于区间问题,大家应该印象深刻,有一周我们专门讲解的区间问题,各种覆盖各种去重。

  • 贪心算法:跳跃游戏(opens new window)

  • 贪心算法:跳跃游戏II(opens new window)

  • 贪心算法:用最少数量的箭引爆气球(opens new window)

  • 贪心算法:无重叠区间(opens new window)

  • 贪心算法:划分字母区间(opens new window)

  • 贪心算法:合并区间(opens new window)

#其他难题

贪心算法:最大子序和 (opens new window)其实是动态规划的题目,但贪心性能更优,很多同学也是第一次发现贪心能比动规更优的题目。

贪心算法:加油站 (opens new window)可能以为是一道模拟题,但就算模拟其实也不简单,需要把while用的很娴熟。但其实是可以使用贪心给时间复杂度降低一个数量级。

最后贪心系列压轴题目贪心算法:我要监控二叉树! (opens new window),不仅贪心的思路不好想,而且需要对二叉树的操作特别娴熟,这就是典型的交叉类难题了。

738. 单调递增的数字

当且仅当每个相邻位数上的数字 x 和 y 满足 x <= y 时,我们称这个整数是单调递增的。

给定一个整数 n ,返回 小于或等于 n 的最大数字,且数字呈 单调递增

示例 1:

输入: n = 10

输出: 9

示例 2:

输入: n = 1234

输出: 1234

示例 3:

输入: n = 332

输出: 299

提示:

  • 0 <= n <= 109

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. 监控二叉树

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

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

计算监控树的所有节点所需的最小摄像头数量。

示例 1:

随想录Day37--贪心算法 : 738.单调递增的数字 , 968.监控二叉树 (可以跳过),总结_第1张图片

输入:[0,0,null,0,0]

输出:1

解释:如图所示,一台摄像头足以监控所有节点。

示例 2:

随想录Day37--贪心算法 : 738.单调递增的数字 , 968.监控二叉树 (可以跳过),总结_第2张图片

输入:[0,0,null,0,null,0,null,null,0]

输出:2

解释:需要至少两个摄像头来监视树的所有节点。 上图显示了摄像头放置的有效位置之一。

提示:

  1. 给定树的节点数的范围是 [1, 1000]。

  1. 每个节点的值都是 0。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
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;
        }
    }
}

你可能感兴趣的:(贪心算法,算法,java,leetcode)