Leetcode分类总结(Greedy)

贪心类题目目前除了正则匹配(Wildcard Matching)(听说实际上是DP)那道还没作其余的免费题目都作了,简单作个总结。html

贪心的奥义就是每一步都选择当前回合”可见范围“(便可得知的信息)内的最优,而在每一步都仅选择当前回合”可见范围“内的最优这一策略下可以致使全局最优的结果的状况使用贪心就会是正确的,不然不适用贪心(或不适用当前对贪心中的最优的定义)。算法

所以,贪心一个点是选择当前最优,另外一个点是这个最优要怎么定义,好比是选使得A最小的仍是选使得A-B或A/B最小的等等。数组

贪心的正确性其实都要经过概括法或反证法等手段进行严格地证实,而这也是算法分析课程的一个重要讲授内容。ui

但对于Leetcode,看到tag是Greedy时可能仍是撇开严格证实赶忙KO比较多o( ̄▽ ̄)d。spa

好比:code

Jump Game(https://leetcode.com/problems/jump-game/)

给定非负整数列表,从0位置起按照所在位置的p值往前跳p个位置,问是否能跳出列表长度。htm

从第0位置开始,咱们p0个“下个位置“能够选,那么选哪一个呢?贪心地选,从”下个位置“能获得的信息也就仅仅是”下个位置“后还能跳多远。blog

按照这个想法那么每次从i位置开始往前跳的这pi个选择里,最优选择j使得i+pj+p[i+pj]最大。leetcode

简单的证实就是,若是我经过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]。it

 

按照前面这个想法,显然每次照最优定义能够一步到位因而能够少跳一些,所以

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==1)
     return 0;
    int nowmax=-1,nowmaxj;
    int count=0,ans;
    i=0;
    while(i<numsSize)
    {
        if(i==0)
        {
            count=0;
        }
        nowmax=i+nums[i];
        count++;
        nowmaxj=i;
        if(nowmax>=numsSize-1)
                break;
        for(j=i+1;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==0||pricesSize==1)
        return 0;
    int nowmin=prices[0];
    int ans=0;
    int i;
    for(i=1;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=0;
    int* candies=(int*)malloc(sizeof(int)*ratingsSize);
    //int candies[50]={0};
    for(i=0;i<ratingsSize;i++)
    {
        if(i==0)
            candies[i]=1;
        else 
        {
            if(ratings[i]>ratings[i-1])//大的状况前面一个+1是显然的
                candies[i]=candies[i-1]+1;
            else if(ratings[i]<=ratings[i-1])//小于等于的其实没法肯定,须要再一遍来肯定
                candies[i]=1;
            
        }
    }
    for(i=ratingsSize-2;i>=0;i--)
    {
        if(ratings[i]>ratings[i+1])
            candies[i]=max(candies[i],candies[i+1]+1);//从左往右获得的数只能再增不能减
    }
    
    for(i=0;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=0,sum=0;
    for(i=0;i<gasSize;i++)
    {
        count+=gas[i]-cost[i];
        if(sum<0){
            sum=gas[i]-cost[i];
            sta=i;
        }
        else
        {
            sum+=gas[i]-cost[i];
        }
    }
    if(count<0)
        return -1;
    
    return sta;
        

}

 

整体而言,贪心就是在每次有多个策略能够选的状况下选当前最优的策略,而最优怎么定义以及要如何证实(好比分状况讨论等)就须要费脑了。。

最后,我哪天心情平稳再刷了正则匹配那道。。

相关文章
相关标签/搜索