實驗室的算法課程,今天輪到我給師弟師妹們講貪心算法,順便也復習一下。
貪心算法這個名字聽起來唬人,其實通常是比較簡單的。雖然通常貪心算法的實現非常容易,但是,一個問題是否能夠使用貪心算法,是一定要小心的。本文課通過LeetCode的一些習題,我們來回顧一下貪心算法。
LeetCode 455. Assign Cookies
題目描述
假設你是一位很棒的家長,想要給你的孩子們一些小餅干。但是,每個孩子最多只能給一塊餅干。對每個孩子 i ,都有一個胃口值 gi ,這是能讓孩子們滿足胃口的餅干的最小尺寸;並且每塊餅干 j ,都有一個尺寸 sj 。如果 sj >= gi ,我們可以將這個餅干 j 分配給孩子 i ,這個孩子會得到滿足。你的目標是盡可能滿足越多數量的孩子,並輸出這個最大數值。
注意:
你可以假設胃口值為正。
一個小朋友最多只能擁有一塊餅干。
示例 1:
輸入: [1,2,3], [1,1]
輸出: 1
解釋:
你有三個孩子和兩塊小餅干,3個孩子的胃口值分別是:1,2,3。
雖然你有兩塊小餅干,由於他們的尺寸都是1,你只能讓胃口值是1的孩子滿足。
所以你應該輸出1。
示例 2:
輸入: [1,2], [1,2,3]
輸出: 2
解釋:
你有兩個孩子和三塊小餅干,2個孩子的胃口值分別是1,2。
你擁有的餅干數量和尺寸都足以讓所有孩子滿足。
所以你應該輸出2.
解題思路
示例2中,可以很輕易的看出,第二塊餅干給第二個小朋友,第一塊餅干給第一個小朋友,就可以使得兩個小朋友都開心。
如果小朋友很多的話,或者餅干很多話,可能不能夠一眼看出怎么分配才能使小朋友都開心,那么怎么才能夠使得更多的小朋友開心呢?
策略:最大的餅干給最貪心的小朋友。
每次都要取得最大的餅干,所以要用到排序算法。在Java中可以直接利用Arrays.sort(int[])
進行排序的操作。其他的語言我不是很清楚,如果沒有排序的API,也可以自己來實現排序算法。可以參考我們一起來排序——使用Java語言優雅地實現常用排序算法
通常,貪心算法和排序是分不開的。
代碼實現
class Solution {
public int findContentChildren(int[] g, int[] s) {
int res = 0;
//先對數組進行排序
Arrays.sort(g);
Arrays.sort(s);
//定義兩個變量,記錄數組的下標
int i = s.length - 1;
int j = g.length - 1;
while(i>=0 && j>=0){
//如果餅干大小大於等於小朋友的胃口,則認為這塊餅干可以使得小朋友滿足。
if(s[i] >= g[j]){
res++;
i--;
j--;
}else{
j--;
}
}
return res;
}
}
時間復雜度分析:while循環的時間復雜度為O(n),調用API進行排序的時間復雜度為O(nlogn),所以整體時間復雜度為O(nlogn)。
LeetCode 122. Best Time to Buy and Sell Stock II
題目描述
給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。
設計一個算法來計算你所能獲取的最大利潤。你可以盡可能地完成更多的交易(多次買賣一支股票)。
注意:你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。
示例 1:
輸入: [7,1,5,3,6,4]
輸出: 7
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 3 天(股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
隨后,在第 4 天(股票價格 = 3)的時候買入,在第 5 天(股票價格 = 6)的時候賣出, 這筆交易所能獲得利潤 = 6-3 = 3 。
示例 2:
輸入: [1,2,3,4,5]
輸出: 4
解釋: 在第 1 天(股票價格 = 1)的時候買入,在第 5 天 (股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接連購買股票,之后再將它們賣出。
因為這樣屬於同時參與了多筆交易,你必須在再次購買前出售掉之前的股票。
示例 3:
輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤為 0。
解題思路
貪心算法:只要在增長就累加到當前的利潤。
代碼實現
class Solution {
public int maxProfit(int[] prices) {
if(prices==null || prices.length==0){
return 0;
}
int res = 0;
for(int i=1;i<prices.length;i++){
//如果當前值大於前一個值,也就是說賣出股票可以獲得利潤
if(prices[i] - prices[i-1] > 0){
res += prices[i] - prices[i-1];
}
}
return res;
}
}
LeetCode 435. Non-overlapping Intervals
題目描述
給定一個區間的集合,找到需要移除區間的最小數量,使剩余區間互不重疊。
注意:
可以認為區間的終點總是大於它的起點
區間 [1,2] 和 [2,3] 的邊界相互“接觸”,但沒有相互重疊。
示例 1:
輸入: [ [1,2], [2,3], [3,4], [1,3] ]
輸出: 1
解釋: 移除 [1,3] 后,剩下的區間沒有重疊。
示例 2:
輸入: [ [1,2], [1,2], [1,2] ]
輸出: 2
解釋: 你需要移除兩個 [1,2] 來使剩下的區間沒有重疊。
示例 3:
輸入: [ [1,2], [2,3] ]
輸出: 0
解釋: 你不需要移除任何區間,因為它們已經是無重疊的了。
解題思路
移除區間的最小數量,也就是說最多保留多少個區間,可以讓這些區間之間互不重疊。
貪心算法:按照區間的結尾進行排序,每次選擇結尾最小的,且和前一個區間不重疊的區間。
代碼實現
這里使用到了二維數組的排序,Java中沒有可以直接使用的API,需要用到Arrays下面的public static <T> void sort(T[] a, Comparator<? super T> c)
,需要重寫創建一個Comparator並重寫compare方法。
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
if(intervals==null || intervals.length == 0){
return 0;
}
//排序
Arrays.sort(intervals,new Comparator<int[]>(){
@Override
public int compare(int[] o1, int[] o2){
if(o1[1] != o2[1]){
return o1[1]-o2[1];
}
return o1[0]-o2[0];
}
});
int res = 1;
int pre = 0;
for(int i=1;i<intervals.length;i++){
if(intervals[i][0] >= intervals[pre][1]){
res++;
pre=i;
}
}
return intervals.length - res;
}
}
總結
貪心選擇性質:在求解一個最優化的問題中,使用貪心的方式選擇了一組內容之后,不會影響剩下的子問題的求解。
如果一個問題滿足貪心選擇性質,則可以使用貪心算法。
證明一個問題是否滿足貪心選擇性質,可以使用反證法和數學歸納法,通常證明過程不簡單。在平時的練習過程中,可以使用舉反例的方法進行驗證,如果舉不出一個反例,則可以嘗試使用貪心算法來求解,通常也會得到正確的結果。