计算机行业找工作五大常用算法之一:贪心算法

本文详细介绍了贪心算法的概念、解题思路,以及它在分发糖果问题、钱币问题、背包问题和区间覆盖问题中的应用。通过实例展示了贪心算法如何寻找局部最优解,帮助解决实际问题。
摘要由CSDN通过智能技术生成

一、定义:
  贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也 就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。
  贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
二、解题思路
  算法的基本思路是从问题的某一个初始解出发一步一步地进行,根据某个优化测度,每一步都要确保能获得局部最优解。每一步只考虑一个数据,他的选取应该满足局部优化的条件。若下一个数据和部分最优解连在一起不再是可行解时,就不把该数据添加到部分解中,直到把所有数据枚举完,或者不能再添加算法停止。
过程:
1)建立数学模型来描述问题;
2)把求解的问题分成若干个子问题;
3)对每一子问题求解,得到子问题的局部最优解;
4)把子问题的解局部最优解合成原来解问题的一个解。
贪心算法有很多经典的应用,比如霍夫曼编码(Huffman Coding)、Prim 和 Kruskal 最小生成树算法、还有 Dijkstra 单源最短路径算法。最小生成树算法和最短路径算法我们后面会讲到,所以我们今天讲下霍夫曼编码,看看它是如何利用贪心算法来实现对数据压缩编码,有效节省数据存储空间的。
三、例子
1、 分发糖果(Leetcode题目)
 老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。

你需要按照以下要求,帮助老师给这些孩子分发糖果:

每个孩子至少分配到 1 个糖果。
相邻的孩子中,评分高的孩子必须获得更多的糖果。
那么这样下来,老师至少需要准备多少颗糖果呢?

示例 1:

输入: [1,0,2]
输出: 5
解释: 你可以分别给这三个孩子分发 2、1、2 颗糖果。
示例 2:

输入: [1,2,2]
输出: 4
解释: 你可以分别给这三个孩子分发 1、2、1 颗糖果。
第三个孩子只得到 1 颗糖果,这已满足上述两个条件。
**贪心算法思想分析:本题目是希望老师发放的糖果最少,题目的条件有两个,1)每个孩子至少得到一个糖果,2)相邻的孩子中,评分高的孩子必须获得更多的糖果。如果说相邻的孩子中,下一个孩子的评分要大,那自然最好的选择就是多给一个糖果最好。

public class Solution {
   
    public int candy(int[] ratings) {
   
        int[] dp = new int[ratings.length];
        dp[0] = 1;
        int sum = 0;
        for(int i = 1; i < ratings.length; i++){
   
           
            if(ratings[i] > ratings[i-1]){
   
                dp[i] = dp[i-1]+1;
            }else {
   
                dp[i]=1;
            }
        }
        for(int i =ratings.length-2;i>=0;i--){
   
            if(ratings[i] > ratings[i+1]&&dp[i]<=dp[i+1]){
   
                dp[i]=dp[i+1]+1;
            }
        }
        for(int i : dp){
   
            sum+=i;
        }
        return sum;
    }
}

2、钱币问题

有1元、2元、5元、10元的纸币分别有a[1], a[2], a[3], a[4]张,要用这些纸币凑出m元,至少要用多少张纸币?

如果是动态规划:
要凑出m元,必须先凑出m-1、m-2、m-5、m-10元,我们用dp[i]表示凑出i元的最少纸币数;
有 dp[i]=min(dp[i-1], dp[i-2], dp[i-5], dp[i-10]) + 1;
容易知道dp[1]=dp[2]=dp[5]=dp[10]=1;
根据以上递推方程和初始化信息,可以容易推出dp[1~m]的所有值。

似乎有些不对?平时我们找零钱有这么复杂吗?
从贪心算法角度出发,当m>10且我们有10元纸币,我们优先使用10元纸币,然后再是5元、2元、1元纸币。
从日常生活的经验知道,这么做是正确的,但是为什么?
假如我们把题目变成这样,原来的策略还能生效吗?

有1元、5元、7元的纸币分别有a[1], a[2], a[3]张,要用这些纸币凑出m元,至少要用多少张纸币?

   接下来我们来分析这种策略:
已知对于m元纸币,1,2,5元纸币使用了a,b,c张,我们有a+2b+5c=m;
假设存在一种情况,1、2、5元纸币使用数是x,y,z张,使用了更少的5元纸币(z<c),且纸币张数更少(x+y+z<a+b+c),即是用更少5元纸币得到最优解。
我们令k=5*(c-z),k元纸币需要floor(k/2)张2元纸币,k%2张1元纸币;(因为如果有2张1元纸币,可以使用1张2元纸币来替代,故而1元纸币只能是0张或者1张)
容易知道,减少(c-z)张5元纸币,需要增加floor(5*(c-z)/2)张2元纸币和(5*(c-z))%2张纸币,而这使得x+y+z必然大于a+b+c。
由此我们知道不可能存在使用更少5元纸币的更优解。
所以优先使用大额纸币是一种正确的贪心选择。

对于1、5、7元纸币,比如说要凑出10元,如果优先使用7元纸币,则张数是4;(1+1+1+7)
但如果只使用5元纸币,则张数是2;(5+5)
在这种情况下,优先使用大额纸币是不正确的贪心选择。(但用动态规划仍能得到最优解)
3、背包问题

问题描述:
一个背包的总容量为V,现在有N类物品,第i类物品的重量为weight[i],价值为value[i]
那么往该背包里装东西,怎样装才能使得最终包内物品的总价值最大。这里装物品主要由三种装法:
1、0-1背包:每类物品最多只能装一次
2、多重背包:每类物品都有个数限制,第i类物品最多可以装num[i]次
3、完全背包:每类物品可以无限次装进包内

3.1:0—1背包
思路分析:
0-1背包问题主要涉及到两个问题的求解

a)求解背包所含物品的最大值:

利用动态规划求最优值的方法。假设用dp[N][V]来存储中间状态值,dp[i][j]表示前i件物品能装入容量为j的背包中的物品价值总和的最大值(注意是最大值),则我们最终只需求知dp[i=N][j=V]的值,即为题目所求。
现在考虑动态规划数组dp[i][j]的状态转移方程:
假设我们已经求出前i-1件物品装入容量j的背包的价值总和最大值为dp[i-1][j],固定容量j的值不变,则对第i件物品的装法讨论如下:
首先第i件物品的重量weight[i]必须小于等于容量j才行,即
1、若weight[i]>j,则第i件物品肯定不能装入容量为j的背包,此时dp[i][j]=dp[i-1][j]
2、若weight[i]<=j,则首先明确的是这件物品是可以装入容量为j的背包的,那么如果我们将该物品装入,则有
dp[i][j]=dp[i-1][j-weight[i]]+value[i]
随之而来的问题是我们要判断第i件物品装到容量为j的背包后,背包内的总价值是否是最大?其实很好判断,即如果装了第i件物品后的总价值dp[i-1][j-weight[i]]+value[i]>没装之前的总价值最大值dp[i-1][j],则肯是最大的;反之则说明第i件物品不必装入容量为j的背包(装了之后总价值反而变小,那么肯定就不需要装嘛)
故,状态转移方程如下:
dp[i][j] = (dp[i-1][j] > (dp[i

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值