贪心算法

  • 问题引入(钞票支付问题)
  • 贪心算法
  • 贪心思路
  • 贪心前提
  • 与动态规划的区别
  • 例题实战(leetcode455)
  • 题目描述
  • 问题思考
  • 贪心规律
  • 算法思路&代码实现


问题引入(钞票支付问题)

有1元、2元、5元、10元、20元、50元、100元的钞票无穷多张。现使用这些钞票支付X元,最少需要多少张???

例如:X = 628元

最佳支付方案为
6张100元,1张20元,1张5元,1张2元,1张1元的;
共需:6+1+1+1+1 = 10张。

直觉告诉我们:尽可能多的使用面值较大的钞票!
贪心法:遵循某种规律,不断贪心的选取当前最优策略的算法设计方法。

为何这样做一定是对的呢??
面额为1元、2元、5元、10元、20元、50元、100元的钞票,每个面额是比自己小的面额的2倍或以上
所以当使用一张较大面额钞票时,若用较小面额钞票替换,需要两张或更多的钞票!!!

例如:

2 = 1+1
5 = 2+2+1
10 = 5+5
20 = 10+10
50 = 20+20+10
100 = 50+50

故,当前最优解即为全局最优解,贪心成立!

/**
 * @ClassName leetcode455
 * @Description :TODO
 * @Author Josvin
 * @Date 2021/02/03/15:48
 */



public class leetcode455{

    public static void main(String[] args) {
        int[] rmb = {100, 50, 20, 10, 5, 2, 1};
        int num = rmb.length;
        int X = 628;
        int count = 0;
        for (int i = 0; i < num ; i++) {
            int tmp = X / rmb[i];
            count += tmp;
            X -= tmp * rmb[i];
            System.out.println("需要面额为" + rmb[i] + "的" + tmp + "张, 剩余需支付RMB" + X + "元");

        }
        System.out.println("总共需要支付" + count + "张");
    }
}

但是:如果增加7元面额,X = 14贪心还成立吗???

java Pattern 贪婪还是非贪婪_java


我们可以看到是不成立的:

贪心解:为3张(10+2+2)

最优解则是:两张(7+7)

我们可以发现当选择了10面值的钞票后,之后贪心的选择就无法修改,也就是不能回退了。当增加7面值贪心就不是最优的了。

贪心算法

贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解

贪心算法每一步必须满足以下条件:

  1. 可行的:即它必须满足问题的约束。
  2. 局部最优:他是当前步骤中所有可行选择中最佳的局部选择。
  3. 不可更改:即选择一旦做出,在算法的后面步骤就不可改变了。

贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。

贪心思路

1.建立数学模型来描述问题。
2.把求解的问题分成若干个子问题。
3.对每一子问题求解,得到子问题的局部最优解
4.把子问题的解局部最优解合成原来解问题的一个解。

贪心前提

贪心策略适用的前提是:局部最优策略能导致产生全局最优解
关键是贪心策略的选择,而贪心算法与动态规划的主要区别是:
贪心选择是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。即贪心选择是采用从顶向下、以迭代的方法做出相继选择,每做一次贪心选择就将所求问题简化为一个规模更小的子问题。

与动态规划的区别

  • 贪心算法的每一次操作都对结果产生直接影响,而动态规划则不是。
  • 贪心算法对每个子问题的解决方案都做出选择,不能回退;动态规划则会根据以前的选择结果对当前进行选择,有回退功能。
  • 能用贪心解决的问题,也可以用动态规划解决

例题实战(leetcode455)

题目描述

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。

对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。(传送门

示例 1:

输入: g = [1,2,3], s = [1,1]
输出: 1
解释: 
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。

示例 2:

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

问题思考

  1. 是否可以直接暴力枚举,对每个饼干都尝试是否可以满足某个孩子?
  2. 当某个孩子可以被多个饼干满足时,是否需要优先用某个饼干满足这个孩子?
  3. 当某个饼干可以满足多个孩子时,是否需要优先满足某个孩子?

贪心规律

首先我们的核心目标是:让更多孩子得到满足,则有如下规律。

  1. 某个饼干如果不能满足某个孩子,则该饼干也不能满足需求因子更大的饼干;
  2. 某个孩子可以用更小的饼干满足,则没必要用更大的饼干满足,因为可以保留更大的糖果满足需求更大的孩子;
  3. 孩子的需求因子更小则其更容易被满足,故优先从需求因子小的孩子尝试,用某个饼干满足一个较大需求因子的孩子或满足一个较小需求因子的孩子的效果时一样的。(最终满足的总量是不变)

算法思路&代码实现

  • 对需求因子和饼干大小数组进行从小到大的排序
  • 按照从小到大的顺序使用各饼干尝试是否可满足某个孩子,每个饼干只尝试一次;若成功,则换下一个孩子尝试;直到发现没更多的孩子或者没有更多的饼干,循环结束。
public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);
        Arrays.sort(s);
        int child = 0;
        int cookie = 0;
        while (cookie < s.length && child < g.length) {
            if (g[child] <= s[cookie]) {
                child++;
            }
            cookie++;
        }
        return child;
    }