贪心算法经典例题 贪心算法

程序员算法基础——贪心算法 贪心是人类自带的能力,贪心算法是在贪心决策上进行统筹规划的统称 。
比如一道常见的算法笔试题---- 跳一跳 :
我们自然而然能产生一种解法:尽可能的往右跳,看最后是否能到达 。
本文即是对这种贪心决策的介绍 。
狭义的贪心算法指的是解最优化问题的一种特殊方法,解决过程中总是做出当下最好的选择,因为具有最优子结构的特点,局部最优解可以得到全局最优解;这种贪心算法是动态规划的一种特例 。能用贪心解决的问题,也可以用动态规划解决 。
而广义的贪心指的是一种通用的贪心策略,基于当前局面而进行贪心决策 。以 跳一跳 的题目为例:
我们发现的题目的核心在于 向右能到达的最远距离,我们用maxRight来表示;
此时有一种贪心的策略:从第1个盒子开始向右遍历,对于每个经过的盒子,不断更新maxRight的值 。
贪心的思考过程类似动态规划,依旧是两步: 大事化小,小事化了。
大事化小:
一个较大的问题,通过找到与子问题的重叠,把复杂的问题划分为多个小问题;
小事化了:
从小问题找到决策的核心,确定一种得到最优解的策略,比如跳一跳中的 向右能到达的最远距离 ;
在证明局部的最优解是否可以推出全局最优解的时候,常会用到数学的证明方式 。
如果是动态规划:
要凑出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]的所有值 。
似乎有些不对? 平时我们找零钱有这么复杂吗?
从贪心算法角度出发,当m10且我们有10元纸币,我们优先使用10元纸币,然后再是5元、2元、1元纸币 。
从日常生活的经验知道,这么做是正确的,但是为什么?
假如我们把题目变成这样,原来的策略还能生效吗?
接下来我们来分析这种策略:
已知对于m元纸币,1,2,5元纸币使用了a,b,c张,我们有a+2b+5c=m;
假设存在一种情况,1、2、5元纸币使用数是x,y,z张,使用了更少的5元纸币(zc),且纸币张数更少(x+y+za+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)
在这种情况下,优先使用大额纸币是不正确的贪心选择 。(但用动态规划仍能得到最优解)
如果是动态规划:
前i秒的完成的任务数,可以由前面1~i-1秒的任务完成数推过来 。
我们用 dp[i]表示前i秒能完成的任务数 ;
在计算前i秒能完成的任务数时,对于第j个任务,我们有两种决策:
1、不执行这个任务,那么dp[i]没有变化;
2、执行这个任务,那么必须腾出来(Sj, Tj)这段时间,那么 dp[i] = max(dp[i], dp[ S[j] ] ) + 1 ;
比如说对于任务j如果是第5秒开始第10秒结束,如果i=10,那么有 dp[i]=max(dp[i], dp[5] + 1); (相当于把第5秒到第i秒的时间分配给任务j)
再考虑贪心的策略,现实生活中人们是如何安排这种多任务的事情?我换一种描述方式:
我们自然而然会想到一个策略: 先把结束时间早的兼职给做了!
为什么?
因为先做完这个结束时间早的,能留出更多的时间做其他兼职 。
我们天生具备了这种优化决策的能力 。
这是一道 LeetCode题目。
这个题目不能直接用动态规划去解,比如用dp[i]表示前i个人需要的最少糖果数 。
因为(前i个人的最少糖果数)这种状态表示会收到第i+1个人的影响,如果a[i]a[i+1],那么第i个人应该比第i+1个人多 。
即是 这种状态表示不具备无后效性 。
如果是我们分配糖果,我们应该怎么分配?
答案是: 从分数最低的开始 。
按照分数排序,从最低开始分,每次判断是否比左右的分数高 。

秒懂生活扩展阅读