目錄
- 定義
- 基本思路
- 動態規划比較
- 舉例說明-55. 跳躍游戲
一、定義
貪心算法(greedy algorithms)(《算法導論(第三版)》第 16 章也有敘述)的定義:在對問題求解時,總是做出在當前看來是最好的選擇。貪心算法不是對所有問題都能得到整體最優解,關鍵是貪心策略的選擇,選擇的貪心策略必須具備無后效性,即某個狀態以前的過程不會影響以后的狀態,只與當前狀態有關。
1.1 適應的問題對象:
貪心策略適用的前提是:局部最優策略能導致產生全局最優解。
實際上,貪心算法適用的情況很少。一般對一個問題分析是否適用於貪心算法,可以先選擇該問題下的幾個實際數據進行分析,就可以做出判斷。
1、需要求解准確解:必須證明每一步所作的貪心選擇最終導致問題的整體最優解。
2、求解近似解:有時候不需要准確求解,為了加快速度,可以考慮貪心算法求解近似解。
1.2 關鍵點
沒有一般化的規則來說明貪心算法是否最優,策略的選取也沒有確定形式,但有兩個基本要點:貪心選擇性質和最優子結構。
貪心選擇性質
第一個關鍵要素就是貪心選擇性質:我們可以做出局部最優選擇來構造全局最優解。也就是說,我們在做出選擇時,總是以當前的情況為基礎做出最優選擇的,而不用考慮子問題的解。
這也是和動態規划最大的不同之處。在動態規划中,在每次做出一個選擇的時候總是要將所有選擇進行比較才能確定到底采用哪一種選擇,而這種選擇的參考依據是以子問題的解為基礎的,所以動態規划總是采用自底向上的方法,先得到子問題的解,再通過子問題的解構造原問題的解。就算是自頂而下的算法也是先求出子問題的解。在貪心算法中,我們總是在原問題的基礎上做出一個選擇,然后求解剩下的唯一子問題,貪心算法從來都不依賴子問題的解,不過有可能會依賴上一次做出的選擇,所以貪心算法是自頂而下的。
最優子結構
如果一個問題的最優解包含其子問題的最優解,那么就稱這個問題具有最優子結構性質。
二、基本思路
貪心算法的基本思路是從問題的某一個初始解出發一步一步地進行,根據某個優化測度,每一步都要確保能獲得局部最優解。每一步只考慮一個數據,他的選取應該滿足局部優化的條件。若下一個數據和部分最優解連在一起不再是可行解時,就不把該數據添加到部分解中,直到把所有數據枚舉完,或者不能再添加算法停止
2.1 流程思路
- 建立數學模型來描述問題
- 把求解的問題分成若干個子問題
- 對每個子問題求解,得到子問題的局部最優解
- 把子問題的解局部最優解合成原來問題的一個解
2.2 實現框架
從問題的某一初始解出發:
while (朝給定總目標前進一步)
{
利用可行的決策,求出可行解的一個解元素。
}
由所有解元素組合成問題的一個可行解;
三、動態規划比較
3.1 相同點
- 都是一種推導算法
- 都是分解成子問題來求解,都需要具有最優子結構
3.2不同點
- 貪心算法和動態規划相比,它既不看前面(也就是說它不需要從前面的狀態轉移過來),也不看后面(無后效性,后面的選擇不會對前面的選擇有影響),因此貪心算法時間復雜度一般是線性的,空間復雜度是常數級別的。
- 貪心算法的每一次操作都對結果產生直接影響,而動態規划則不是。貪心算法對每個子問題的解決方案都做出選擇,不能回退;動態規划則會根據以前的選擇結果對當前進行選擇,有回退功能。
- 動態規划主要運用於二維(也可以一維)問題,而貪心一般是處理一維問題。
- 動態規划因為要使用子問題順序結果,因此一般是自底向上,貪心算法沒有相關要求。
四、舉例說明-55. 跳躍游戲
4.1、問題
給定一個非負整數數組,你最初位於數組的第一個位置。數組中的每個元素代表你在該位置可以跳躍的最大長度。判斷你是否能夠到達最后一個位置。
示例 1:
輸入: [2,3,1,1,4]
輸出: true
解釋: 我們可以先跳 1 步,從位置 0 到達 位置 1, 然后再從位置 1 跳 3 步到達最后一個位置。
示例 2:
輸入: [3,2,1,0,4]
輸出: false
解釋: 無論怎樣,你總會到達索引為 3 的位置。但該位置的最大跳躍長度是 0 , 所以你永遠不可能到達最后一個位置。
4.2、理解
子問題:設想一下,對於數組中的任意一個位置 y,我們如何判斷它是否可以到達?根據題目的描述,只要存在一個位置 x,它本身可以到達,並且它跳躍的最大長度為 x + nums[x],這個值大於等於 y,即 x+nums[x]≥y,那么位置 y 也可以到達。
步驟:我們依次遍歷數組中的每一個位置,並實時維護最遠可以到達的位置。對於當前遍歷到的位置 x,如果它在 最遠可以到達的位置 的范圍內,那么我們就可以從起點通過若干次跳躍到達該位置,因此我們可以用 x+nums[x] 更新最遠可以到達的位置。
如果 最遠可以到達的位置 大於等於數組中的最后一個位置,那就說明最后一個位置可達,直接返回 True 。反之,如果在遍歷結束后,最后一個位置仍然不可達,我們就返回 False 作為答案。
4.3、代碼
class Solution:
def canJump(self, nums: List[int]) -> bool:
n , _max = len(nums) , 0
for i in range(n):
if i <= _max: #這一行判斷一定要有,表示能到達,例如[3, 2, 1, 0, 4]就不能跨過第三位。
_max = max(_max,i+nums[i])
if _max >= n-1: #注意這里是n-1,比如[0]或者上面的題目。
return True
return False
