貪心算法:給我最好的,現在就要!

 

每次做選擇的時候都做出當下最好的選擇,而不考慮將來的後果。並且期望最終得到的結果是全局最優的。

——貪心算法 - Greedy Algorithm

什麼時候該使用貪心算法

針對一組數據,定義了限制值。現在需要我們從中選出幾個數據,在滿足限制值的情況下,使期望值最大。

這個不難理解,比如知乎上這個很火的問題:如果帶着一百萬人民幣回到1997年,你會做什麼投資? 對於這個問題,一百萬和1997年就是限制值,你在這個條件下可選的各種投資方案就是上面說的“一組數據”,要做的就是經過我們的運作將收益最大化(使期望值最大)。很明顯貪心的我們會優先選擇低成本而高回報的項目。

使用貪心算法的例子

  • 分糖果
  • 無重疊區間
  • 錢幣找零

分糖果

我們要把m個大小不同的糖果分給n個貪婪不一的孩子,糖果的大小大於或等於孩子的滿足感時才能滿足這個孩子,怎樣才能滿足讓儘量多孩子滿足?

在這個例子中,限制了糖果的數量,並期望滿足儘量多孩子,這就要求我們運用恰當的方式來選出儘量多的孩子。小孩子是天真的,成年人是貪心的。只需要用剛好能滿足孩子的糖果就能使他滿足,我們就分小糖果給滿足感低的孩子,大糖果可以用來滿足滿足度高的孩子,這樣就可以滿足儘量多的孩子。所以我們的做法是:挑出滿足度最低的孩子,給他最小的能滿足他的糖果,重複這個操作直到糖果分完。

LeetCode#455. 分發餅乾:Assign cookies

    public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g); // 將孩子的滿足度從小到大排序
        Arrays.sort(s); // 將糖果的大小從小到大排序
        int gi = 0, si = 0;
        while(gi < g.length && si < s.length){
            if(g[gi] <= s[si]){
                gi++; // 當前小孩子得到滿足,就換下一個孩子
            }
            si++; // 當前的糖果分出去就換下一個糖果;沒有分出去就說明這個糖果太小沒人要,也得換下一個糖果
        }
        return gi;
    }

無重疊區間

給定n個區間和每個區間的起始與結束座標,這些區間可能存在重疊的情況,我們要移除掉一些區間以使剩下的區間不重疊,最少要移除多少個區間?

假設給定一組區間爲:[[6, 8], [2, 4], [3, 5], [1, 5], [5, 9], [8, 10]],求最少需要移除多少個區間。爲了保留更多區間,保留下來的區間就應該儘量下且互相不重疊。我們先將所有區間按照結束位置end來排序,由於排序之後end越小的區間的位置就更靠左,留給右邊的位置也越多,就能選擇出更多的區間。如此一來,只要將排序後的區間遍歷一遍,如果當前區間的開始位置小於前一個區間的結束位置,就說明產生了重疊應該移除掉;否則就是應該保留的區間。

排序後的區間

LeetCode#435. 無重疊區間:Non-overlapping Intervals

class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        if(intervals == null || intervals.length < 1) return 0;
        
        int cnt = 0;
        // 先按每個區間的end排序
        Arrays.sort(intervals, new MyComparator());
        int end = intervals[0][1];
        for(int i = 1; i < intervals.length; i++){
            // 如果當前得start 小於之前的 end,就說明有重合,需要移除。則計數並跳過本次循環
            if(intervals[i][0] < end){
                cnt++;
            }else{
                end = intervals[i][1];
		   }
        }
        return cnt;
        
    }
    class MyComparator implements Comparator<int[]>{
        @Override
        public int compare(int[] a, int[] b){
            return a[1] - b[1];
        }
    }
}

錢幣找零

這是個很貼近日常生活的例子。假如我們有面值爲1、5、10、50、100的紙幣若干,它們的張數分別爲c1、c5、c10、c50、c100。現在我們需要支付K元,最少需要多少張紙幣呢?

在支付額K恆定時,要想付出更少的紙幣數量,就要優先使用大面值來支付。限定值是金額K,而期望是付出最少紙幣數量。假設買了一個烤冷麪,需要398元。先從包裏數三張100的紅票票,此時還差98元;再來一張50的,還差48;二十元的面值不流通了,我們只好拿出4張10元的;最後是一張5元和4張1元烤冷麪就到手了。當然還有一個問題是我們的紙幣數量有限,如果我們沒有三張100元,就要提前讓50元登場。這個問題你理解了嗎?沒理解也沒關係,請出示你的付款碼,我掃你。

小結

其實在能用貪心算法的場景中,貪心都是能得到最優解的。在數學上也是可以證明最優解的有效性的。貪心算法適用的場景就那些,只要多多練習當你再次碰見這些場景時就能遊刃有餘得想出解題思路了。

貪心算法的應用

  • 對數據壓縮編碼的霍夫曼編碼(Huffman Coding)
  • 求最小生成樹的Prim算法和Kruskal算法
  • 求單源最短路徑的Dijkstra算法
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章