力扣刷题指南——贪心算法

一、什么是贪心算法

        贪心算法(英语:greedy algorithm),又称贪婪算法,是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是最好或最优的算法。(维基百科)

        贪心算法的核心在于每次都要获取当前状态下最优的解,我这里举个通俗易懂的例子。你是个店家,你手头上只有馒头没有钱。顾客排队购买你的馒头,一次只购买一个馒头。然后顾客只能向你支付5毛、1块、2块,你必须给每个顾客正确的找零。为了我们手头上能有足够的钱给下一个顾客找零,我们需要保证每一步都尽可能让自己手头上的5毛钱最多,这样就可以给后面的顾客找零了,这样一种思路就是贪心算法了。

        贪心算法的实现大致就是:(step1)从某个初始解出发;(step2)采用迭代的过程,当可以向目标前进一步时,就根据局部最优策略,得到一部分解,缩小问题规模;(step3)将所有解综合起来。

二、LeetCode典型题详解

860.柠檬水找零(easy)【找零问题】

题目描述:

在柠檬水摊上,每一杯柠檬水的售价为 5 美元。

顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。

每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。

注意,一开始你手头没有任何零钱。

如果你能给每位顾客正确找零,返回 true ,否则返回 false 。

输出样例:

输入:[5,5,5,10,20]
输出:true
解释:
前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。
第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。
第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。
由于所有客户都得到了正确的找零,所以我们输出 true。

题解:

这个题目是很典型的找零问题。因为5美元的张数是最实用的,所以使用贪心的思路,每次找零的话,如果能用一张10美元进行找零,绝对不使用两张5美元进行找零。我们需要借用三个变量存放5美元、10美元、20美元的数量,如果找零后5美元或者10美元的数量,则返回False,否则可以一直进行下去。

代码:

class Solution {
    public boolean lemonadeChange(int[] bills) {
        int len=bills.length;
        int a=0,b=0,c=0;
        if(bills[0]>5) return false; //如果第一个顾客支付的不是5美元,则无法进行找零,直接输出False
        if(len==1&&bills[0]==5) return true;//如果只有一个顾客,且支付的是5美元,则直接输出True
        for(int i=0;i<len;i++){
            if(bills[i]==5){
                a++;
            }else if(bills[i]==10){
                a--;
                if(a<0) return false;
                b++;
            }else{
                if(b==0){//如果没有一张10美元的钞票,只能忍痛给三张5美元进行找零了
                    a -=3;
                }else{
                    a--;
                    b--;
                }
                if(a<0||b<0) return false;
                c++;
            }
        }
        return true;
    }
}

 

455.分发饼干(Easy)【分配问题】

题目描述:

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。

输出样例:

输入: g = [1,2], s = [1,2,3]
输出: 2
解释: 
你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。
你拥有的饼干数量和尺寸都足以让所有孩子满足。
所以你应该输出2.

题解:

每个孩子只能给一块饼干,饼干的大小都不一致。那么使用贪心算法的思想,要让每一步都达到当前最优,则胃口最小的孩子应该最先得到饼干,所以我们得先把大于等于这个孩子胃口的饼干大小中找到最小的饼干给这个孩子。用代码描述的话,就是先把胃口大小和饼干大小进行从小到大排序,然后依次帮孩子寻找适合孩子胃口且最小的饼干。

代码:

import java.util.Arrays;
class Solution {
    public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);
        Arrays.sort(s);
        int child=0;
        int cookie=0;
        while(child<g.length && cookie<s.length){
            if (g[child]<=s[cookie]) child++;
            cookie++;
        }
        return child;
    }
}

134.加油站(Medium)

题目描述:

在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。

如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。

说明: 

如果题目有解,该答案即为唯一答案。
输入数组均为非空数组,且长度相同。
输入数组中的元素均为非负数。

输出样例:

输入: 
gas  = [1,2,3,4,5]
cost = [3,4,5,1,2]

输出: 3

解释:
从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
因此,3 可为起始索引。

题解:

这个题目用暴力方法是可以实现的,有兴趣的网友可以尝试一下。这题采用贪心的算法就是要从i加油站到i+1加油站的cost要大于汽车拥有的汽油量,如果不是的话,那就从下一个地方开始,不然就从现在作为起点。

这个题目我们用一个for循环即可达到,重点在于我们要设置一个变量total_cost和current,这个total_cost用来存放总的汽车内的汽油情况,current用来存放从index开始到当前位置的汽油情况,如果current>0说明从index作为起点的话,那么消耗汽油量是超过汽车加油的量的,所以不能从index作为起点,需选择下一个i+1作为起点index,并将current设置为0。

最终我们的判断是看total_cost,如果总的消耗量大于加油量(即total>0),则返回-1,否则返回index。

代码:

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
       //total_cost是一整个过程中所需要的总消耗(消耗与加油的差值)
       //index 是开始的位置
       //current是从开始的index到当前的cost(消耗与加油的差值)
       int total_cost=0,index=0,current=0;
       for(int i=0;i<gas.length;i++)
       {
           total_cost +=cost[i]-gas[i];
           current += cost[i]-gas[i];
           //如果当前的current>0了,说明消耗大于加油了,则从这个i位置开始是不行的,所以将current置为0
           //从下一个作为开始index
           if(current>0){
               index=i+1;
               current=0;
           }
       }
       if(total_cost<=0) return index;
       else 
       return -1;
    }
}

435.无重叠区间(Medium)

题目描述:

给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。

注意:

可以认为区间的终点总是大于它的起点。
区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠

输出样例:

输入: [ [1,2], [2,3], [3,4], [1,3] ]

输出: 1

解释: 移除 [1,3] 后,剩下的区间没有重叠。

题解:

这个题目采取的贪心策略就是保留末尾最小的区间,这个是怎么实现的呢?首先我们先按照区间未的大小将区间进行从小到大的排序,接下来每次选择结尾最小且前一个选择的区间不重叠的区间。在这里怎么判断重叠呢?如果一个区间的开头小于前面区间的末尾,则判断这个区间是重叠的,需要被移除。如果没办法想象的话,我们可以根据样例和下面的代码画个区间轴进行理解。

代码:

import java.util.Arrays;
class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        int len=intervals.length;
        if(len<=1) return 0;
        //Arrays.sort(intervals, Comparator.comparingInt(o -> o[0]));//按行进行排序
        Arrays.sort(intervals, Comparator.comparingInt(o -> o[1]));//按列进行排序
        int total=0;
        int prev=intervals[0][1];
        for(int i=1;i<len;i++){
            if(intervals[i][0]<prev) total++;
            else prev=intervals[i][1];
        }
        return total;
    }
}

452.用最少数量的箭引爆气球(Medium)

题目描述:

在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以y坐标并不重要,因此只要知道开始和结束的x坐标就足够了。开始坐标总是小于结束坐标。平面内最多存在104个气球。

一支弓箭可以沿着x轴从不同点完全垂直地射出。在坐标x处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足  xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。

输出样例:

输入:
[[10,16], [2,8], [1,6], [7,12]]

输出:
2

解释:
对于该样例,我们可以在x = 6(射爆[2,8],[1,6]两个气球)和 x = 11(射爆另外两个气球)。

题解:

这个问题是一个很典型的区间重叠问题,解决这个问题比较好的方法就是使用贪心算法。
首先我们按照end位置的大小对数组进行从小到大进行排序,依次查看有多少个区间是重叠的,重叠的区间则打一次。

用代码实现就是依次寻找直到找到一个不与prev重叠的区间,则前面打了一次。依次进行,当然,最后还需要补上一次。各位可以在草稿纸上画坐标进行查看,应该就很容易明白我的思路了。
但是我的解法应该不是最优的,因为在排序那里的时间复杂度较大。

代码:

import java.util.Arrays;
class Solution {
    public int findMinArrowShots(int[][] points) {
        int len=points.length;
        int total=0;
        if(len==0) return 0;
        Arrays.sort(points, Comparator.comparingInt(o -> o[1]));//按列进行排序
        int prev=points[0][1];
        for(int i=1;i<len;i++){
            if(points[i][0]>prev){
                total++;
                prev=points[i][1];
            } 
        }
        return total+1;//最后那个也要打掉
    }
}

三、补充练习题

392.判断子序列(Easy)

122.买卖股票最佳时机||(Easy)

605.种花问题(Easy)

763.划分字母区间(Medium)

406.根据身高重建队列(Medium)

1386.安排电影院位置(Medium)

四、其他

1、本博客引用的题目均来自LeetCode。

2、本博客会不定期更新题目、题解,也欢迎大家收藏评论转发。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值