貪心算法Greedy筆記與應用

貪心算法是一種在每一步選擇中都採用在當前狀態下最好或最優(即最有利)的選擇, 從而希望導致結果是全局最好或者
最優的算法.(用貪心算法,尤其是最基礎的貪心,每次當下情況下找到最優不一定能夠達到全局最優的情況)
貪心算法與動態規劃的不同在於它對每個子問題的解決方案都做出選擇,不能回退.動態規劃會保存以前的運算結果, 並根據以前的結果對當前進行選擇, 有回退功能.

對比:

貪心: 當下做局部最優判斷
回溯: 能夠回退
動態規劃: 最優判斷 + 回退 (帶最優判斷的回溯,我們叫做動態規劃,動態規劃會保存以前的運算結果, 並根據以前保存的結果, 對當前進行選擇有回退功能)

貪心法可以解決一些最優化問題, 如: 求圖中的最小生成樹,求哈夫曼編碼等. 然而對於工程和生活中的問題, 貪心法一般不能得到我們想要的答案, 一旦一個問題可以通過貪心法來解決,那麼貪心法一般是解決這個問題的最好辦法.由於貪心法的高效性以及其所求得的答案比較接近最優結果, 貪心法也可以用作輔助算法,或者解決一些要求結果不特別精確的問題

應用實例 零錢兌換

322 零錢兌換

/*
給定不同面額的硬幣 coins 和一個總金額 amount。編寫一個函數來計算可以湊成總金額所需的最少的硬幣個數。如果沒有任何一種硬幣組合能組成總金額,返回 -1。

示例 1: 輸入: coins = [1, 2, 5], amount = 11
輸出: 3 
解釋: 11 = 5 + 5 + 1

示例 2: 輸入: coins = [2], amount = 3 
輸出: -1
說明: 你可以認爲每種硬幣的數量是無限的。
*/

當硬幣可選集合固定: Coins = [20, 10, 5, 1]
求最少可以幾個硬幣拼出總數, 比如total = 36
首先用20先去匹配, 20最多可以匹配多少個,我們就把20能匹配的個數全部都用總數減去,現在總數爲36,所以總數爲1個,也就是36除以20,整除除得多少表示20可以用多少個,這裏只有一個,那麼得到16, 再看10, 在看5, 在看1, 所以這裏貪心法是先用最大的去匹配, 再用次大的再匹配
在這裏插入圖片描述
爲什麼可以用貪心法,是因爲硬幣的話20 10 5 1前面的硬幣依次是後面這些硬幣的倍數, 所以如果你需要用兩個10,或者4個5的話,肯定還不如用一個20,因爲後面都是整除前面最大的硬幣,貪心法每次用最大的即可, 既然用20,那麼肯定後面這些會不優於直接選20的情況, 貪心法有它的特殊性, 這個硬幣有整除的關係,所以用貪心法, 但在大部分情況下
在這裏插入圖片描述

適用貪心算法的場景

簡單地說, 問題能夠分解成子問題來解決, 子問題的最優解能遞推到最終問題的最優解,這種子問題最優解稱爲最優子結構.
貪心算法與動態規劃的不同在於它對每個子問題的解決方案都做出選擇,不能回退.動態規劃則會保存以前的運算結果, 並根據以前結果對當前進行選擇, 有回退功能

在現實生活中很少用到貪心, 你要是每天只滿足自己當下的需求, 你覺得遊戲好就打遊戲,或者餓了飽喫一頓, 每天都喫很多類似於這樣的東西, 最後發現長期是達不到最優解的,比如時間都浪費在一些可能提高自己的短期比較痛苦但長期的話,對你有益的事情上面,以及要自律,保持體重,或者是要去健身這些, 這些都是短期讓你痛苦,長期讓你受益的行爲

分發餅乾 思想 先排序, 排序之後從小到大匹配兩個升序的數組
# 僞代碼 時間複雜度爲O(N), N表示小孩胃口和餅乾長度較小者
res = 0;
q.sort()
s.sort()
g_length = len(g)
s_length = len(s)
i = 0
j = 0
while i < g_length and j < s_length:
	if g[i] <= s[j]:
		#可以滿足胃口, 把小餅乾餵給小朋友
		res += 1
		i += 1
		j += 1
	else: 
	    # 不滿足胃口, 查看下一塊小餅乾
		j += 1
return res
最佳買賣股票時期 輸入[7, 1, 5, 3, 6 ,4] 輸出7, 在1這買入,在5這賣出,賺4元, 然後在3買入,在6賣出,賺3元 , 4+3 = 7這就是所謂的結果, 輸入[1, 2, 3, 4, 5]在第一天買入, 一直是前一天買後一天拋直到最後,賺4元.所以這個思路就是隻要後一天比前一天大,我們就在前一天買入,後一天拋
// 一
public int maxProfit(int[] prices) {
	int maxProfit = 0;
	for (int i = 1; i < prices.length; i++) {
		if (prices[i] > prices[i - 1]) {
			maxProfit += prices[i] - prices[i - 1];
		}	
	}
	return maxProfit;
}

// 二
public int maxProfit(int[] prices) {
	int maxProfit = 0;
	for (int i = 0; i < prices.length - 1; i++) {
		if (prices[i + 1] > prices[i]) {
			maxProfit += prices[i + 1] - prices[i];
		}	
	}
	return maxProfit;
}
跳躍遊戲 從後往前貪心
// 例如輸入爲 [2, 3, 1, 1, 4]   或者輸入爲[3, 2, 1, 0, 4]
public boolean canJump(int[] nums) {
	if (nums == null) {
		return false;
	}
	int endReachable = nums.length - 1;
	for (int i = nums.length - 1; i >= 0; i--) {
	 // 表示從i這個點最多可以跳多遠, 如果它最多可以跳的距離大於最後的一個能夠跳到最終點的下標的話, 說明i的話是可以跳到最後點的, 那麼就把i更新到endReachable, endReachable始終指定是最前面一個下標它能夠跳到最後點, 那麼這個從最後往前不斷進行循環,且每時候都檢查,如果當前的i能夠跳到最後的話, 那麼則把i更新到endReachable,  最後判斷endReachable是不是爲0, 是0的話表示第一個座標的位置也可以跳到最後, 爲什麼這用到貪心,是因爲只要記能夠跳到最後的那個位置的第一個值就是記這麼一個最前者的值, 就節省了一個數組來記錄重要的結果, 當然也可以節省一層循環
		if (nums[i] + i >= endReachable) {
			endReachable = i;
		}
	}
	return endReachable == 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章