算法通过村第十七关-贪心|白银笔记|贪心高频问题


前言


提示:如果生活把你的门关上了,那你就再打开,这就是门,门就是这样的。 --佚名

贪婪的思想不一定要理解的很透彻,但是贪婪的问题我们要掌握的很好,这里我们就研究一些高频的考察题目。

区间问题

区间问题就是典型的面试中常见到的,这类面试题还是很受欢迎的,容易考察应聘者到底会不会写好代码。我们看看一些区间的例子🌰.两个区间的所有可能关系如下所示:

对于所有区间的问题,我们还是看看下面的图,一定要记住这些关系,很常见,也很容易出。

在这里插入图片描述

判断区间是否重复

参考地址连接:【leetcode】252 会议室(数组)_给定一个会议时间安排的数组-CSDN博客

在这里插入图片描述
看下图🥰:
在这里插入图片描述
因为一个人同一时刻只能参加一个会议,因此题目实质是判断是否存在重叠区间的。将区间按照会议开始时间进行排序,然后遍历一遍判断后面的会议开始的时候是否前面的会议还没有结束即可,也就是上面的图中所显示的,如果存在重叠直接返回false即可。

    /**
     * 会议室(数组)
     *
     * @param intervals
     * @return
     */
    public static boolean canAttendMeetings(int[][] intervals) {
        //将区间排序
        Arrays.sort(intervals, (v1, v2) -> v1[0] - v2[0]);

        // 遍历所有会议,是否出现重复 ( 技巧 从1 开始
        for (int i = 1; i < intervals.length; i++) {
            if (intervals[i][0] < intervals[i - 1][1]) {
                return false;
            }
        }
        return true;
    }

合并区间

参考题目参数:56. 合并区间 - 力扣(LeetCode)

在这里插入图片描述
在这里插入图片描述
看下图🥰:
在这里插入图片描述
和上一题一样,首先对区间按照起点端点进行升序排序,然后逐个判断当前区间是否与前一个区间重叠,如果不重叠的话直接加入结果集,反之重叠的话(当前区间与前一个区间进行合并)

 /**
     * 合并区间
     * @param intervals
     * @return
     */
    public static int[][] merge(int[][] intervals) {
        // 排序数组
        Arrays.sort(intervals,(v1,v2) ->v1[0] - v2[0]);
        // 创建新的区间
        int[][] merge = new int[intervals.length][2];
        int idx = -1;
        // 遍历数组
        for(int [] interval: intervals){
            // 如果数组是空的或者当前区间的起始位置 > 结果数组中最后区间的最大值
            // 不合并区间,直接加入区间
            if (idx == -1 && interval[0] > merge[idx][1]){
                merge[++idx] = interval;
            }else{
                // 反之说明重叠,则将当前区间合并至结果数组的最后区间
                merge[idx][1] = Math.max(merge[idx][1],interval[1]);
            }
        }
        return Arrays.copyOf(merge, idx + 1);
    }

插入区间

参考题目地址:57. 插入区间 - 力扣(LeetCode)

在这里插入图片描述
本题目也是上一个题目的扩展,这里区间已经按照起始结束升序排列了,我们可以直接遍历区间,寻找新区间的插入位置就可以了。

  1. 首先经新新区间左边且相离的区间加入结果集(遍历时,如果当前区间的结束位置,小于新区见的开始位置,说明当前区间在新区间的左边且相离)。
  2. 接着判断当前区间是否与新区间重叠,重叠的话就进行合并,知道遍历到当前区间在新区间的右边且相离,将最终合并后的新区间加入结果集;
  3. 最后将结果集右边且相离的区间加入结果集。
    /**
     * 插入区间
     * @param intervals
     * @param newInterval
     * @return
     */
    public static int[][] insert(int[][] intervals, int[] newInterval) {
        // 创建一个足够大的数组
        int[][] res = new int[intervals.length + 1][2];
        int idx = 0;
        // 左边的先进入(intervals[i][1] < newInterval[0]
        //(后面还用的到
        int i = 0;
        while(i < intervals.length && intervals[i][1] < newInterval[0] ){
            res[++idx] = intervals[i++];
        }
        // 合并新区间
        while(i < intervals.length && intervals[i][0] <= newInterval[1]){
            // 最左端
            newInterval[0] = Math.min(newInterval[0], intervals[i][0]);
            // 最右端
            newInterval[1] = Math.min(newInterval[1],intervals[i][0]);
            i++;
        }
        // 将合并的区间放入结果集
        res[idx++]= newInterval;
        // 放入右边的,直接放入结果集
        while(i < intervals.length){
            res[idx++]= intervals[i++];
        }

        return Arrays.copyOf(res,idx);
    }

字符串分割

参考题目地址:763. Partition Labels - 力扣(LeetCode)

在这里插入图片描述

在这里插入图片描述
这个问题,有点回溯。分割问题是回溯算法的典型应用,但是这个问题如果用回溯将很复杂。题目要求同一个字母最多出现在一个片段中,那么要如何进行分片才算合理呢?🤔

该遍历过程相当于找每个字母的边界,如果知道之前遍历过的所有字母的最远边界,说明这个边界就是分割点了,此时前面出现的所有字母,最远也就到这个边界,所以做如下操作:

  • 首先,统计每一个字符最后出现的位置
  • 然后从头遍历字符,并更新字符的最远出现的下标,如果找到字符最远出现的下标和当前下标相等,则找到分割点。

在这里插入图片描述

直接上代码:这个太神了

   /**
     * 划分字母区间
     *
     * @param S
     * @return
     */
    public static List<Integer> partitionLabels(String S) {
        List<Integer> res = new ArrayList<Integer>();
        int[] edge = new int[26];
        char[] chars = S.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            // 只保留最后一次的位置
            edge[chars[i] - 'a'] = i;
        }
        int idx = 0;
        int last = -1;
        for (int i = 0; i < chars.length; i++) {
            idx = Math.max(idx, edge[chars[i] - 'a']);
            if (i == idx) {
                // 这个非常巧妙
                res.add(i - last);
                last = i;
            }
        }
        return res;
    }

加油站问题

参考题目介绍:134. 加油站 - 力扣(LeetCode)

在这里插入图片描述
在这里插入图片描述

我们说做贪婪的题目,如果不管什么是贪婪,就直接做题。这里解释以下题目的含义:

实例给我们了两个数组,知识代表了当前站提供的油料,以及从前一个站过来需要消耗多少油料,因为是环,所以这里gas[0] = 1 和gas[0] = 3 就表示第一个站有1升汽油,从第一个站到第二个站需要消耗3升汽油.

当然最简单的方式是暴力从第一个试到最后一个。如图:

在这里插入图片描述
我们一直向后找,可以的话是一定有答案的。

但是,这个问题需要大量的重复计算,优化以下:

  1. 首先,如果总的油量大于(等于)总的消耗量,那么是可以跑完一圈的,具体就每段我们要考虑以下了。在具体一点是各个站的剩余油量rest[i]相加一点是大于等于零的。
  2. 每个加油站的剩余量rest[i]为gas[i] - cost[i].从i到0开始累加rest[i],和基座curSum,一旦curSum小于零,说明[0,i]区间都不能做起始位置,起始位置必须从i + 1开始重新算,只有这样才能保证我们有可能完成。
    在这里插入图片描述
    代码展示:
  public int canCompleteCircuit(int[] gas, int[] cost) {
        int start = 0;
        int totalSum = 0;
        int curSum = 0;
        for(int i = 0; i < gas.length; i++){
            curSum += gas[i] - cost[i];
            totalSum += gas[i] - cost[i];
            if(curSum < 0){
                // 更新起始位置
                start = i + 1;
                // 更新curSum
                curSum = 0;
            }
        }
        // 说明不能走一圈
        if(totalSum < 0){
            return -1;
        }
        return start;
    }

总结

提示:区间问题;字符串分割;加油站;贪婪算法;区间划分


如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ ("▔□▔)/

如有不理解的地方,欢迎你在评论区给我留言,我都会逐一回复 ~

也欢迎你 关注我 ,喜欢交朋友,喜欢一起探讨问题。
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

师晓峰

啤酒饮料矿泉水,你的打赏冲一冲

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值