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;
        

}

 

整體而言,貪心就是在每次有多個策略能夠選的狀況下選當前最優的策略,而最優怎麼定義以及要如何證實(好比分狀況討論等)就須要費腦了。。

最後,我哪天心情平穩再刷了正則匹配那道。。

相關文章
相關標籤/搜索