贪心类题目目前除了正则匹配(Wildcard Matching)(据说其实是DP)那道还没做其他的免费题目都做了,简单做个总结。
贪心的奥义就是每一步都选择当前回合”可见范围“(即可得知的信息)内的最优,而在每一步都仅选择当前回合”可见范围“内的最优这一策略下能够导致全局最优的结果的情况使用贪心就会是正确的,否则不适用贪心(或不适用当前对贪心中的最优的定义)。
因此,贪心一个点是选择当前最优,另一个点是这个最优要怎么定义,比如是选使得A最小的还是选使得A-B或A/B最小的等等。
贪心的正确性其实都要通过归纳法或反证法等手段进行严格地证明,而这也是算法分析课程的一个重要讲授内容。
但对于Leetcode,看到tag是Greedy时可能还是撇开严格证明赶紧KO比较多o( ̄▽ ̄)d。
比如:
Jump Game(https://leetcode.com/problems/jump-game/)
给定非负整数列表,从0位置起按照所在位置的p值往前跳p个位置,问是否能跳出列表长度。
从第0位置开始,我们p0个“下个位置“可以选,那么选哪个呢?贪心地选,从”下个位置“能得到的信息也就仅仅是”下个位置“后还能跳多远。
按照这个想法那么每次从i位置开始往前跳的这pi个选择里,最优选择j使得i+pj+p[i+pj]最大。
简单的证明就是,如果我通过j选择下一次能跳到最大的i+pj+p[i+pj],而选择其他的k,从k再跳一次都到不了i+pj+p[i+pj],那么显然从k要么永远跳不到i+pj+p[i+pj],要么必须再经过一个中间点才能到i+pj+p[i+pj],所以不如我一步到位跳到i+pj+p[i+pj]。
按照前面这个想法,显然每次照最优定义可以一步到位于是可以少跳一些,因此
Jump Game II(https://leetcode.com/problems/jump-game-ii/)
求跳出去的最小步数也就可以照着解了。
这里仅贴Jump Game II的代码:
#define min(a,b) ((a)<(b)?(a):(b))
int jump(int* nums, int numsSize) {
int i,j;
if(numsSize==)
return ;
int nowmax=-,nowmaxj;
int count=,ans;
i=;
while(i<numsSize)
{
if(i==)
{
count=;
}
nowmax=i+nums[i];
count++;
nowmaxj=i;
if(nowmax>=numsSize-)
break;
for(j=i+;j<=i+nums[i];j++)//贪心地每一次跳跃目标j都是到一个j+nums[j]更远的点作为跳跃目标
{
if(j+nums[j]>nowmax)
{
nowmaxj=j;
nowmax=j+nums[j];
} }
i=nowmaxj;
} return count;
}
Best Time to Buy and Sell Stock II(https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/)
一个股价序列,不限制交易次数,只是不允许两次交易之间有重叠(在边界重叠则可以),要赚最多钱。
策略就是从第一个位置开始,遇到比持有低的i就换手(假装前面没买,现在买),遇到高的i就出手并买入新的。
分两种情况讨论:
①如果后面遇到比i高的j,这里按i卖的收益也不会丢,然后再按i入货,到后面高的j出货前后收益和在i不卖等到这时候j卖一样多。
②如果后面遇到比i低的j,那显然我先在i卖出去的肯定赚。
因此前面的策略成立。
代码:
int maxProfit(int* prices, int pricesSize) {
if(pricesSize==||pricesSize==)
return ;
int nowmin=prices[];
int ans=;
int i;
for(i=;i<pricesSize;i++)
{
if(prices[i]>nowmin)
{
ans+=prices[i]-nowmin;
nowmin=prices[i]; }
else
nowmin=prices[i];
}
return ans;
}
Candy和Gas Station
这两道是我觉得比较难的。。
先说Candy。
按照优先级序列分糖,高优先级的人要比它旁边的低优先级的人分得多,而每人最少一颗糖。
为了最少,所以从左往右如果优先级是递增那么分的糖果数就依次+1,当然,为了最少起始那个人肯定是1。
那么如果出现优先级下降的情况呢,贪心地分我先分他个1,待会再说。
当然,这么贪心地给1可能会有问题,比如我下一个更少,还给1就不满足要求了。
所以末了需要从右往左再扫一遍修正这个遗留问题以保证如果p[i]>p[i+1],那么最少candy[i]是candy[i+1]+1。注意这里是最少,因为可能在第一躺中,为了candy[i]满足规则candy[i]已经比candy[i+1]+1大了,那么这时候为了延续之前对规则的满足是不能改成candy[i+1]+1的。
代码:
#define max(a,b) ((a)>(b)?(a):(b))
int candy(int* ratings, int ratingsSize) {
int i,j;
int count=;
int* candies=(int*)malloc(sizeof(int)*ratingsSize);
//int candies[50]={0};
for(i=;i<ratingsSize;i++)
{
if(i==)
candies[i]=;
else
{
if(ratings[i]>ratings[i-])//大的情况前面一个+1是显然的
candies[i]=candies[i-]+;
else if(ratings[i]<=ratings[i-])//小于等于的其实无法确定,需要再一遍来确定
candies[i]=; }
}
for(i=ratingsSize-;i>=;i--)
{
if(ratings[i]>ratings[i+])
candies[i]=max(candies[i],candies[i+]+);//从左往右得到的数只能再增不能减
} for(i=;i<ratingsSize;i++)
count+=candies[i];
free(candies);
return count;
}
我觉得最难的Gas Station来了。。
给两个序列,一个是i位置能补充多少汽油,一个是从i位置到i+1位置需要多少汽油。可以*选起点,问能不能转个圈,能的话告知起点位置。
我还是直接引用http://www.cnblogs.com/felixfang/p/3814463.html的解释吧:
假设从站点 i 出发,到达站点 k 之前,依然能保证油箱里油没见底儿,从k 出发后,见底儿了。那么就说明 diff[i] + diff[i+1] + ... + diff[k] < 0,而除掉diff[k]以外,从diff[i]开始的累加都是 >= 0的。也就是说diff[i] 也是 >= 0的,这个时候我们还有必要从站点 i + 1 尝试吗?仔细一想就知道:车要是从站点 i+1出发,到达站点k后,甚至还没到站点k,油箱就见底儿了,因为少加了站点 i 的油。。。
因此,当我们发现到达k 站点邮箱见底儿后,i 到 k 这些站点都不用作为出发点来试验了,肯定不满足条件,只需要从k+1站点尝试即可!因此解法时间复杂度从O(n2)降到了 O(2n)。之所以是O(2n),是因为将k+1站作为始发站,车得绕圈开回k,来验证k+1是否满足。
等等,真的需要这样吗?
我们模拟一下过程:
a. 最开始,站点0是始发站,假设车开出站点p后,油箱空了,假设sum1 = diff[0] +diff[1] + ... + diff[p],可知sum1 < 0;
b. 根据上面的论述,我们将p+1作为始发站,开出q站后,油箱又空了,设sum2 = diff[p+1] +diff[p+2] + ... + diff[q],可知sum2 < 0。
c. 将q+1作为始发站,假设一直开到了未循环的最末站,油箱没见底儿,设sum3 = diff[q+1] +diff[q+2] + ... + diff[size-1],可知sum3 >= 0。
要想知道车能否开回 q 站,其实就是在sum3 的基础上,依次加上 diff[0] 到 diff[q],看看sum3在这个过程中是否会小于0。但是我们之前已经知道 diff[0] 到 diff[p-1] 这段路,油箱能一直保持非负,因此我们只要算算sum3 + sum1是否 <0,就知道能不能开到 p+1站了。如果能从p+1站开出,只要算算sum3 + sum1 + sum2 是否 < 0,就知都能不能开回q站了。
因为 sum1, sum2 都 < 0,因此如果 sum3 + sum1 + sum2 >=0 那么 sum3 + sum1 必然 >= 0,也就是说,只要sum3 + sum1 + sum2 >=0,车必然能开回q站。而sum3 + sum1 + sum2 其实就是 diff数组的总和 Total,遍历完所有元素已经算出来了。因此 Total 能否 >= 0,就是是否存在这样的站点的 充分必要条件。
这样时间复杂度进一步从O(2n)降到了 O(n)。
代码:
int canCompleteCircuit(int* gas, int gasSize, int* cost, int costSize) {
int i,j,sta; int count=,sum=;
for(i=;i<gasSize;i++)
{
count+=gas[i]-cost[i];
if(sum<){
sum=gas[i]-cost[i];
sta=i;
}
else
{
sum+=gas[i]-cost[i];
}
}
if(count<)
return -; return sta; }
总体而言,贪心就是在每次有多个策略可以选的情况下选当前最优的策略,而最优怎么定义以及要如何证明(比如分情况讨论等)就需要费脑了。。
最后,我哪天心情平稳再刷了正则匹配那道。。
Leetcode分类总结(Greedy)的更多相关文章
-
LeetCode 分类颜色
LeetCode 分类颜色 给定一个包含红色.白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色.白色.蓝色顺序排列. 此题中,我们使用整数 0. 1 和 ...
-
leetcode 分类
https://www.douban.com/note/330562764/ https://blog.csdn.net/Irving_zhang/article/details/78835035 h ...
-
【LeetCode】贪心 greedy(共38题)
[44]Wildcard Matching [45]Jump Game II (2018年11月28日,算法群衍生题) 题目背景和 55 一样的,问我能到达最后一个index的话,最少走几步. 题解: ...
-
Leetcode分类刷题答案&;心得
Array 448.找出数组中所有消失的数 要求:整型数组取值为 1 ≤ a[i] ≤ n,n是数组大小,一些元素重复出现,找出[1,n]中没出现的数,实现时时间复杂度为O(n),并不占额外空间 思路 ...
-
leetcode Wildcard Matching greedy algrithm
The recursive program will result in TLE like this: class Solution { public: bool isMatch(const char ...
-
LeetCode分类-前400题
1. Array 基础 27 Remove Element 26 Remove Duplicates from Sorted Array 80 Remove Duplicates from Sorte ...
-
leetcode一刷总结,明天二刷
1:基础的数据结构:图掌握极差,二叉树次之 2:常用的算法思想:dp,深度有先,广度优先等等. 3:优化以解决的题目,注意思想的总结 4:将约150道题都刷掉 5:优先解决设计算法思想的题目类别,其次 ...
-
面试总结之Google
准备Google面试的总结 - 知乎 https://zhuanlan.zhihu.com/p/40866467 [不周山之算法与数据结构]壹 总览 | 小土刀 https://wdxtub.com/ ...
-
LeetCode56:Jump Game
Given an array of non-negative integers, you are initially positioned at the first index of the arra ...
随机推荐
-
书柜的尺寸(bzoj 1933)
Description Tom不喜欢那种一字长龙式的大书架,他只想要一个小书柜来存放他的系列工具书.Tom打算把书柜放在桌子的后面,这样需要查书的时候就可以不用起身离开了.显然,这种书柜不能太大,To ...
-
python进程、线程、协程(转载)
python 线程与进程简介 进程与线程的历史 我们都知道计算机是由硬件和软件组成的.硬件中的CPU是计算机的核心,它承担计算机的所有任务. 操作系统是运行在硬件之上的软件,是计算机的管理者,它负责资 ...
-
简单的web三层架构系统【第二版】
昨天写了 web三层架构的第一版,准确的说是三层架构的前期,顶多算是个二层架构,要慢慢完善. 第一版里,程序虽说能运行起来,但是有一个缺陷,就是里面的SQL语句,是使用的拼接字符进行执行.这样安全系数 ...
-
(转)收集:Hibernate中常见问题 No row with the given identifier exists问题的原因及解决
Hibernate中No row with the given identifier exists问题的原因及解决 产生此问题的原因: 有两张表,table1和table2.产生此问题的原因就是tab ...
-
APC注入
0X01 注入原理 当线程被唤醒时APC中的注册函数会被执行的机制,并依此去调用我们的DLL加载代码,进而完成注入的目的 具体的流程: 1 当EXE里的某个线程执行到sleepEX(),或者waitF ...
-
谈一谈EasyUI的TreeGrid的过滤功能
写在最前面 这个星期一直在纠结easyui的treegrid的过滤功能,原因呢,自然是项目中一个莫名奇妙的需求. easyui虽说是后端程序员的前端框架,但是说句实话,除去api,让我直接写里面的节点 ...
-
C# Ioc容器Unity,简单实用
开头先吐槽一下博客园超级不好用,添加图片后就写不动字了,难道是bug 好进入正题,先来说下依赖注入,简单来说就是定义好接口,上层代码调用接口,具体实现通过配置文件方式去指定具体实现类. 首先我们需要通 ...
-
linux批量压缩当前目录中文件后,删除原文件
linux批量压缩当前目录中文件后,删除原文件 for i in `ls|awk -F " " '{print $NF}'`; do tar -zcvf $i.tar.gz $i ...
-
Windows7 IIS 出现错误,并非所有的功能被成功更改
(1)进入UAC,将其调至最低. 具体操作方法:点击[开始],在程序搜索中输入UAC,回车后则进入UAC控制界面. UAC,用户帐户控制设置如下,这个设置主要是避免Windows7种由于用户帐户权限控 ...
-
Perl:写POD文档
官方手册:https://perldoc.perl.org/perlpod.html POD文档是perl的man文档,可以用perldoc输出,也可以直接用man输出.在开始下面的文章之前,请先粗略 ...