最易懂的贪心算法

贪心算法

概念:

顾名思义,贪心算法或贪心思想采用贪心的策略,保证每次操作都是局部最优的,从而使最后得到的结果是全局最优的。

举一个最简单的例子:小明和小王喜欢吃苹果,小明可以吃五个,小王可以吃三个。已知苹果园里有吃不完的苹果,求小明和小王一共最多吃多少个苹果。在这个例子中,我们可以选用的贪心策略为,每个人吃自己能吃的最多数量的苹果,这在每个人身上都是局部最优的。又因为全局结果是局部结果的简单求和,且局部结果互不相干,因此局部最优的策略也同样是全局最优的策略。



分配类问题

445.分发饼干
在这里插入图片描述

题解:
因为饥饿度最小的孩子最容易吃饱,所以我们先考虑这个孩子。为了尽量使得剩下的饼干可以满足饥饿度更大的孩子,所以我们应该把大于等于这个孩子饥饿度的、且大小最小的饼干给这个孩子。满足了这个孩子之后,我们采取同样的策略,考虑剩下孩子里饥饿度最小的孩子,直到没有满足条件的饼干存在。

简而言之,这里的贪心策略是,给剩余孩子里最小饥饿度的孩子分配最小的能饱腹的饼干。
至于具体实现,因为我们需要获得大小关系,一个便捷的方法就是把孩子和饼干分别排序。这样我们就可以从饥饿度最小的孩子和大小最小的饼干出发,计算有多少个对子可以满足条件。

    public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);
        Arrays.sort(s);
        int j=0;
        int res = 0;
        for(int i = 0;i<s.length;++i){
            if(j>=g.length) break;
            if(g[j]<=s[i]){
                res++;
                j++;
            }
        }
        return res;
    }



135.分发糖果
在这里插入图片描述
题解:
做完了题目 455,你会不会认为存在比较关系的贪心策略一定需要排序或是选择?虽然这一道题也是运用贪心策略,但我们只需要简单的两次遍历即可:把所有孩子的糖果数初始化为 1;先从左往右遍历一遍,如果右边孩子的评分比左边的高,则右边孩子的糖果数更新为左边孩子的糖果数加 1;再从右往左遍历一遍,如果左边孩子的评分比右边的高,且左边孩子当前的糖果数不大于右边孩子的糖果数,则左边孩子的糖果数更新为右边孩子的糖果数加 1。通过这两次遍历,分配的糖果就可以满足题目要求了。这里的贪心策略即为,在每次遍历中,只考虑并更新相邻一侧的大小关系。
在样例中,我们初始化糖果分配为 [1,1,1],第一次遍历更新后的结果为 [1,1,2],第二次遍历更新后的结果为 [2,1,2]。

    public int candy(int[] ratings) {
        int len = ratings.length;
        int res = len;
        int[] targer=new int[len];
        for(int i =len-2;i>=0;--i){
            if(ratings[i]>ratings[i+1]&&targer[i]<=targer[i+1]){
                res+=targer[i+1]+1-targer[i];
                targer[i]=targer[i+1]+1;
                
            }
        }
        for(int i =0;i<len-1;++i){
            if(ratings[i]<ratings[i+1]&&targer[i]>=targer[i+1]){
                res+=targer[i]+1-targer[i+1];
                targer[i+1]=targer[i]+1;
            }
        }
        return res;
    }




区间问题

435.无重叠区间
在这里插入图片描述
题解:
在选择要保留区间时,区间的结尾十分重要:选择的区间结尾越小,余留给其它区间的空间就越大,就越能保留更多的区间。因此,我们采取的贪心策略为,优先保留结尾小且不相交的区间。
具体实现方法为,先把区间按照结尾的大小进行增序排序,每次选择结尾最小且和前一个选择的区间不重叠的区间。
在样例中,排序后的数组为 [[1,2], [1,3], [2,4]]。按照我们的贪心策略,首先初始化为区间[1,2];由于 [1,3] 与 [1,2] 相交,我们跳过该区间;由于 [2,4] 与 [1,2] 不相交,我们将其保留。因此最终保留的区间为 [[1,2], [2,4]]

注意:需要根据实际情况判断按区间开头排序还是按区间结尾排序。

    public int eraseOverlapIntervals(int[][] intervals) {
        Arrays.sort(intervals, (int[] o1,int[] o2)->{
            return o1[1]-o2[1];
        });
        
        int res = 1;
        int j = 0;
        for(int i = 1;i<intervals.length;++i){
            if(intervals[i][0]>=intervals[j][1]){
                res++;
                j=i;
            }
        }
        return intervals.length-res;
    }



练习:

PS:以下题目均来自LeetCode

基础难度:

605.Can Place Flowers (Easy)
采取什么样的贪心策略,可以种植最多的花朵呢?

452.Minimum Number of Arrows to Burst Balloons (Medium)
这道题和题目 435 十分类似,但是稍有不同,具体是哪里不同呢?

763.Partition Labels (Medium)
为了满足你的贪心策略,是否需要一些预处理?

注意:在处理数组前,统计一遍信息(如频率、个数、第一次出现位置、最后一次出现位置等)可以使题目难度大幅降低。

122.Best Time to Buy and Sell Stock II (Easy)
股票交易题型里比较简单的题目,在不限制交易次数的情况下,怎样可以获得最大利润呢?


进阶难度

406.Queue Reconstruction by Height (Medium)
温馨提示,这道题可能同时需要排序和插入操作。

665.Non-decreasing Array (Easy)
需要仔细思考你的贪心策略在各种情况下,是否仍然是最优解。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值