1. 程式人生 > >資料結構和演算法躬行記(6)——貪心演算法

資料結構和演算法躬行記(6)——貪心演算法

  貪心演算法(Greedy Algorithm)會在每一步選擇中都採取當前狀態下最好或最優(即最有利)的選擇,不能回退,從而希望結果是最好或最優的演算法。它是動態規劃的一種特例,需要滿足更多的限制條件。

  貪心演算法在有最優子結構的問題中尤為有效(例如求圖的最小生成樹、哈夫曼編碼等),最優子結構是指區域性最優解能決定全域性最優解。即問題能夠分解成子問題來解決,子問題的最優解能遞推到最終問題的最優解。

一、區間排程

  給定多個 [start, end] 的區間集合,算出有多少個不重疊的區間。例如 [1,3], [2,4], [3,6],有兩個不重疊的區間 [1,3], [3,6],因為邊界相互接觸,並不算重疊。例題:435. 無重疊區間。

  解題思路如下所列:

  (1)根據終點對區間進行排列。

  (2)從區間集合中選取一個終點最小的區間 [start, minEnd]。

  (3)將所有與 [start, minEnd] 相交的區間從集合中移除。

  (4)重複執行(2)和(3),直至遍歷完集合。

  具體實現程式碼如下所示。

function eraseOverlapIntervals(intervals) {
  intervals.sort((a, b) => a[1] - b[1]);
  let curEnd = intervals[0],          //終點最小的區間
    count = 1;                        //不重疊的區間數
  intervals.forEach((value) => {
    if (value[0] < curEnd[1]) {       //過濾起點比curEnd終點小的區間
      return;
    }
    count++;
    curEnd = value;
  });
  return count;
}

二、分糖果

  假設有 m 塊糖果和 n 個小孩,但是 m > n,即糖果少,小孩多,會有一部分小孩分不到糖果。

  每塊糖果的大小不等,這 m 個糖果的大小分別是s1,s2,s3,……,sm。每個小孩最多分到一塊糖果,並且每個小孩對糖果大小的需求也不同,他們的需求分別是g1,g2,g3,……,gn。

  如果 sj >= gi ,那麼可以將這個餅乾 j 分配給小孩 i ,這個小孩會得到滿足。該如何分配糖果,才能滿足最多數量的小孩。例題:455. 分發餅乾。

  解題思路是每次從剩下的小孩中,找出對糖果需求最小的,然後發給他當前糖果中能滿足他的最小糖果,如下所示。

function findContentChildren(g, s) {
  g.sort((a, b) => a - b);        //升序排列
  s.sort((a, b) => a - b);        //升序排列
  let n = 0,
    m = 0,
    count = 0;                    //滿足的小孩人數
  while (n < g.length && m < s.length) {
    if (g[n] <= s[m]) {           //小孩能夠得到滿足
      n++;
      m++;
      count++;
      continue;
    }
    m++;
  }
  return count;
}

三、錢幣找零

  假設買一杯牛奶需要5元,顧客向你支付5 元、10 元或 20 元紙幣,你需要判斷是否能正確找零(開始時手頭沒有零錢)。例題:860. 檸檬水找零。

  解題思路是先用面值最大的紙幣來找零,不夠的話再用更小一點的面值,直至完成找零或無法找零,如下所示。

function lemonadeChange(bills) {
  let five = 0,     //5元紙幣數量
    ten = 0;        //10元紙幣數量
  for (let bill of bills) {
    switch (bill) {
      case 5:
        five++;                //增加5元紙幣數量
        break;
      case 10:
        if (five > 0) {        //有5元才紙幣能找零
          five--;
        } else {
          return false;
        }
        ten++;
        break;
      case 20:
        if (five > 0 && ten > 0) {        //用5元和10元兩種紙幣找零
          five--;
          ten--;
        } else if (five >= 3) {           //用3張5元紙幣找零
          five -= 3;
        } else {
          return false;
        }
        break;
    }
  }
  return true;
}

&n