贪心算法—LeetCode135.分发糖果

 

首先,为大家介绍一下贪心算法的核心思想:保证每次操作都是局部最优,从而使最终得到的结果最优。这种结果并不是必然的,但是往往是正确的,原因是:在很多的情况中,全局最优即为局部最优的简单求和。

接下来以leetcode中的一道题目为例:


题目描述:

n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。

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

  • 每个孩子至少分配到 1 个糖果。
  • 相邻两个孩子评分更高的孩子会获得更多的糖果。

请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。


 

题目给出的条件非常明确,我们已有的信息就是ratings数组,而需要达到的要求有两个:

1、每个孩子分有一个糖果

2、相邻两个孩子评分更高的孩子会获得更多的苹果


我一直比较提倡的是先理清整个思路、确定算法的可行性之后,再开始操作,但在这里为了方便对应相关代码,我直接将最终思路写在这里了,但我还是建议大家先理清思路后,再开始操作,可以省去很多不必要的麻烦。

接下来,我们对题目进行分析:

第一个条件很容易达成,既然要求每个孩子都至少有一个糖果,那么我们将其初始化为1不就好了嘛!

int length=ratings.size();
        
vector<int> CandyNum(length,1);

而第二个条件,就要用到我们的贪心算法了,要保证相邻两个孩子都评分更高的有更高的苹果,那我们能否对这个条件进行拆分呢?相邻孩子不就是左孩子和有孩子嘛(除开头结尾的两个孩子以外),那我们就可以将这个规则一分为2:

  • 左规则:当 ratings[i - 1] < ratings[i]时,i号学生的糖果数量将比 i−1 号孩子的糖果数量多。
  • 右规则:当 ratings[i] >ratings[i+1] 时,ii号学生的糖果数量将比 i+1 号孩子的糖果数量多。

以此为基础,我们便可以进行两次遍历:从左往右一次,判断评分是否比右邻孩子大,若是,则将其糖果数改为右邻孩子糖果数+1。

注意:不是当前值加1,而是改为右孩子的糖果数加1!因为我们要求的是有更多的糖果数,光加1个不一定会使当前糖果数大于下一个。

同理,我们可以从右往左再一次遍历,只不过这次比较的是:是否比左孩子大。

注意:在第二次遍历时(从右往左),不要忘记还有一个判定条件:评分高糖果数少的情况下,才需要更改糖果数,若他本身糖果数就大于相邻孩子的话,则不需要更改。第一次遍历没有增加这个判断条件只是因为一定不会存在左糖果数小于右糖果数的情况,因为右面的糖果数均为1,所以不需要判断。

基于以上分析,我们便可以写下以下代码:

for(int i=1;i<ratings.size();i++)
        {
            if(ratings[i]>ratings[i-1])
            {
                CandyNum[i]=CandyNum[i-1]+1;
            }
        }
//这里要注意,ratings.size()是个数,因此在查看数组对应值的时候,要减1
//而又因为要与右面元素比较,所以直接从倒数第二个孩子开始向前遍历,即ratings.size()
for(int j=ratings.size()-2;j>=0;j--){
            if(ratings[j]>ratings[j+1]&&CandyNum[j]<=CandyNum[j+1])
                CandyNum[j]=CandyNum[j+1]+1;
}

最后,我们只需要返回糖果总数即可:

return accumulate(CandyNum.begin(),CandyNum.end(),0);

最终测试没有问题:


可能大家会对这种方法存在疑问,两次遍历的方法看似符合逻辑,但是否能禁得住数学的严密推导呢?这里我截取了leetcode讨论区中的一段分析,我个人觉得是很有道理的,大家可以参考一下:

为什么取最大值是正确的思考:

很多人说这个问题显而易见,不值得讨论,但我相信还是有人像我一样不理解,在这里说一下我的想法

我疑惑的问题不是取最大值为啥是最优解,而是取最大值后为啥不影响某一规则的成立。

我们取序列中的任意两点,A B

  1. 如果 A > B ,则按照左规则处理后,B不会比A多;按照右规则处理后,A一定比B多,那么A一定会被更新(变大),但L、R规则仍然成立:B不会比A多,A一定比B多;
  2. 同理可讨论 A<B;
  3. 当 A == B,A、B的值无论如何更新,都不影响 L、R规则

综上,取最大值后不影响某一规则的成立。


以上就是对该题的分析解释了。在这里我帮大家附上原题链接,大家可以尝试敲一遍代码来加深记忆。此外leetcode讨论区中有还有很多的优质解答供大家参考。

如果大家存在任何疑问,也欢迎在评论区中留言,我也会尽力为大家解答!!

by undo&toutu

原题链接:

135. 分发糖果 - 力扣(LeetCode) (leetcode-cn.com)icon-default.png?t=M0H8https://leetcode-cn.com/problems/candy/description/

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值