图解啥是贪心算法?

它是动态规划的特例,所以如果能用贪心来解的也都可以用动态规划来解。

文章转载自图解啥是贪心算法?五分钟学算法

前言

本文将会从以下几个方面来介绍贪心算法

  • 什么是贪心算法
  • 贪心算法例题详题
  • 贪心算法适用场景
  • 再看三角形最短路径和是否能用贪心算法求解

什么是贪心算法

贪心算法是指在每个阶段做选择的时候都做出当前阶段(或状态)最好的选择,并且期望这样做到的结果是全局最优解(但未必是全局最优解)

贪心算法其实是动态规划的一种,由于它的「贪心」,只着眼于当前阶段的最优解,所以每个子问题只会被计算一次,如果由此能得出全局最优解,相对于动态规划要对每个子问题求全局最优解,它的时间复杂度无疑是会下降一个量级的。

举个简单的例子,比如给定某个数字的金额(如 250)与 100, 50, 10, 5, 1 这些纸币(不限量),怎么能用最少张的纸币来兑换这张金额呢,显然每次兑换应该先从大额的纸币兑换起,第一次选 100, 第二次还是选 100, 第三次选第二大的 50 元,每次都选小于剩余金额中的最大面额的纸币,这样得出的解一定是全局最优解!时间复杂度无疑是线性的。

我们先来看几道可以用贪心算法来求解的例题

贪心算法例题详解

分糖果

有 m 个糖果和 n 个孩子。我们现在要把糖果分给这些孩子吃,但是糖果少,孩子多(m < n),所以糖果只能分配给一部分孩子。每个糖果的大小不等,这 m 个糖果的大小分别是s1,s2,s3,……,sm。除此之外,每个孩子对糖果大小的需求也是不一样的,只有糖果的大小大于等于孩子的对糖果大小的需求的时候,孩子才得到满足。假设这 n 个孩子对糖果大小的需求分别是 g1,g2,g3,……,gn。那么如何分配糖果,能尽可能满足最多数量的孩子呢?

求解:这道题如果用贪心来解解题思路还是比较明显的,对于每个小孩的需求 gn,只要给他所有大小大于 gn 的糖果中的最小值即可,这样就能把更大的糖果让给需求更大的小孩。整个代码如下:

注意:记得要两个数组都排序啊~~~

public class Solution {
    /**
     *  获取能分配给小孩且符合条件的最多糖果数
     */
    private static int dispatchCandy(int[] gList, int[] sList) {
        Arrays.sort(gList);     // 小孩对糖果的需求从小到大排列
        Arrays.sort(sList);     // 糖果大小从小到大排列

        int maximumCandyNum = 0;
        for (int i = 0; i < gList.length; i++) {
            for (int j = 0; j < sList.length; j++) {
                // 选择最接近小孩需求的糖果,以便让更大的糖果满足需求更大的小孩
                if (gList[i] <= sList[j]) {
                    maximumCandyNum++;
                    // 糖果被选中,将其置为-1,代表无效了
                    sList[j] = -1;
                    // 当前小孩糖果已选中,跳出
                    break;
                }
            }
        }
        return maximumCandyNum;
    }

    public static  void main(String[] args) {
        // 小孩对糖果的需求
        int[] gList = {1,2,4,6};
        // 糖果实际大小
        int[] sList = {1,2,7,3};
        int result = dispatchCandy(gList, sList);
        System.out.println("result = " + result);
    }
}

无重叠区间

给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。注意:可以认为区间的终点总是大于它的起点。区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。
示例 1: 输入: [ [1,2], [2,3], [3,4], [1,3] ] 输出: 1 解释: 移除 [1,3] 后,剩下的区间没有重叠。
示例 2: 输入: [ [1,2], [1,2], [1,2] ] 输出: 2 解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。
示例 3: 输入: [ [1,2], [2,3] ] 输出: 0 解释: 你不需要移除任何区间,因为它们已经是无重叠的了。

可以使用动态规划进行解题,参考之前做的——LeetCode 435. Non-overlapping Intervals

接下来重点来了,来看看如何用贪心算法来求解。首先要把各个区间按照区间的终点从小到大排列,如下
在这里插入图片描述
我们的思路与上文中的动态规划一样,先求出最大不重叠子区间个数,再用「区间总数-最大不重叠子区间个数」即为最小要移除的重叠区间数。

用贪心算法求最大不重叠子区间个数步骤如下:

  1. 选择终点最小的区间,设置为当前区间 cur
  2. 按区间终点从小到大寻找下一个与区间 cur不重叠的区间,然后将此区间设置为当前区间 cur(注意此时最大不重叠子区间个数要加1),不断重复步骤 2, 直到遍历所有的区间。

动图如下,相信大家看完动图会更容易理解
在这里插入图片描述
知道了解题思路,写代码就很简单了

/**
 * 贪心算法求解
 */
private static Integer removeSubDuplicateWithGreedy(Interval[] intervals) {
    // 将区间终点由小到大进行排序
    Arrays.sort(intervals, Comparator.comparingInt(a -> a.end));

    int cur = 0;            // 设置第一个为当前区间
    int count = 1;      // 最大不重叠区间数,最小为1
    for (int i = 1; i < intervals.length; i++) {
        // 不重叠
        if (intervals[cur].end < intervals[i].start) {
            cur = i;
            count++;
        }
    }
    // 总区间个数减去最大不重叠区间数即最小被移除重叠区间
    return intervals.length - count;
}

时间复杂度是多少呢,只有一个循环,所以是 O ( n ) O(n) O(n), 比起动态规划的 O ( n 2 ) O(n^2) O(n2),确实快了一个数量级,简单分析下为啥贪心算法这么快,由以上代码可以看到,它只关心眼前的最优解(选择下一个与当前区间不重叠的区间再依次遍历,选完之后再也无需关心之前的区间了!)而动态规划呢,从它的 dp 方程(dp[i] = max{dp[j]} + 1)可以看出,对于每个 i ,都要自底向上遍历一遍 0 到 i 的解以求出最大值,也就是说对于动态规划的子问题而言,由于它追求的是全局最优解,所以它有一个回溯(即自底向上求出所有子问题解的最优解)的过程,回溯的过程中就有一些重复的子问题计算,而贪心算法由于追求的是眼前的最优解,所以不会有这种回溯的求解,也就省去了大量的操作,所以如果可以用贪心算法求解,时间复杂度无疑是能上升一个量级的。

贪心算法使用场景

简单总结一下贪心算法,它指的是每一步只选最优的,并且期望每一步选择的最优解能达成全局的最优解,说实话这太难了,因为一般一个问题的选择都会影响下一个问题的选择,除非子问题之间完全独立,没有关联,比如我们在文中开头说的凑零钱的例子, 如果一个国家的钞票比较奇葩,只有 1,5,11 这三种面值的钞票,如何用最少的钞票凑出 15 呢,如果用贪心第一次选 11, 那之后只能选 4 张 1 了,即 15 = 1 x 11 + 4 x1。其实最优解应该是 3 张 5 元的钞票,为啥这种情况下用贪心不适用呢,因为第一次选了 11,影响了后面钞票的选择,也就是说子问题之间并不是独立的,而是互相制约,互有影响的,所以我们选贪心的时候一定要注意它的适用场景。

再看三角形最短路径和是否能用贪心算法求解

回过头来看开头的问题,三角形最短路径和能否用贪心算法求解呢

先回顾一下这个题目:
在这里插入图片描述
如图示,以上三角形由一连串的数字构成,要求从顶点 2 开始走到最底下边的最短路径,每次只能向当前节点下面的两个节点走,如 3 可以向 6 或 5 走,不能直接走到 7。
在这里插入图片描述
如图示:要求节点 2 到底部的最短路径,它只关心节点 9, 10,之前层数的节点无需再关心!因为 9,10 已经是最优子结构了,所以只保存每层节点(即一维数组)的最值即可!

如果用贪心算法怎么求解

1、 第一步:由 2 往下走,由于 3 比 4 小,所以选择 3
在这里插入图片描述
2、 第二步:由 3 往下走,由于 5 比 6 小,所以选择 5
在这里插入图片描述
3、第三步: 从 5 往下走, 1 比 8 小,选择 1
在这里插入图片描述
答案是 11 ,与动态规划得出的解一模一样!那是否说明这道题可以用贪心算法求解?

答案是否定的!上面的解之所以是正确的,是因为这些数字恰好按贪心求解出来得出了全局最优解,如果我们换一下数字,看看会如何
在这里插入图片描述
如图示,如果数字换成如图中所示,则按贪心得出的最短路径是 66, 而实际上最短路径应该为 16,如下图所示
在这里插入图片描述
为啥用贪心行不通呢,因为贪心追求的是每一步眼前的最优解,一旦它作出了选择,就会影响后面子问题的选择,比如如果选择了 3,就再也没法选择 7 了!所以再次强调,一定要注意贪心的适用场景,子问题之间是否相互制约,相互影响!

总结

本文简单讲述了贪心算法的适用场景,相信大家对贪心的优劣性应该有了比较清晰的认识,贪心追求的是眼前最优解(要最好的,就现在!)

不管这次选择对后面的子问题造成的影响,所以贪心求得解未必是全局最优解,这就像我们做职业规划一样,千万不可因为一时的利益只考虑当下的利益,要作出对长远的职业生涯能持续有益的选择, 所以贪心的使用场景比较小,它是动态规划的特例,所以如果能用贪心来解的也都可以用动态规划来解。

题型训练

  1. 区间问题
  2. 【贪心】LeetCode 55. Jump Game
  3. 【贪心】LeetCode 406. Queue Reconstruction by Height
  4. ⭐⭐⭐⭐⭐【贪心】LeetCode 621. Task Scheduler
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值