55.跳躍遊戲
45.跳躍遊戲 ii
-----------
經常有讀者在後台問,動態規劃和貪心演算法到底有啥關係。我們之前的文章 貪心演算法之區間排程問題 就說過乙個常見的時間區間排程的貪心演算法問題。
說白了,貪心演算法可以理解為一種特殊的動態規劃問題,擁有一些更特殊的性質,可以進一步降低動態規劃演算法的時間複雜度。那麼這篇文章,就講 leetcode 上兩道經典的貪心演算法:跳躍遊戲 i 和跳躍遊戲 ii。
我們可以對這兩道題分別使用動態規劃演算法和貪心演算法進行求解,通過實踐,你就能更深刻地理解貪心和動規的區別和聯絡了。
ps:我認真寫了 100 多篇原創,手把手刷 200 道力扣題目,全部發布在 labuladong的演算法小抄,持續更新。建議收藏,按照我的文章順序刷題,掌握各種演算法套路後投再入題海就如魚得水了。
跳躍遊戲 i 是 leetcode 第 55 題,難度是 medium,但實際上是比較簡單的,看題目:
不知道讀者有沒有發現,有關動態規劃的問題,大多是讓你求最值的,比如最長子序列,最小編輯距離,最長公共子串等等等。這就是規律,因為動態規劃本身就是運籌學裡的一種求最值的演算法。
那麼貪心演算法作為特殊的動態規劃也是一樣,也一定是讓你求個最值。這道題表面上不是求最值,但是可以改一改:
請問通過題目中的跳躍規則,最多能跳多遠?如果能夠越過最後一格,返回 true,否則返回 false。
bool canjump(vector& nums)
return farthest >= n - 1;
}
你別說,如果之前沒有做過類似的題目,還真不一定能夠想出來這個解法。每一步都計算一下從當前位置最遠能夠跳到**,然後和乙個全域性最優的最遠位置farthest
做對比,通過每一步的最優解,更新全域性最優解,這就是貪心。
很簡單是吧?記住這一題的思路,看第二題,你就發現事情沒有這麼簡單。。。
ps:我認真寫了 100 多篇原創,手把手刷 200 道力扣題目,全部發布在 labuladong的演算法小抄,持續更新。建議收藏,按照我的文章順序刷題,掌握各種演算法套路後投再入題海就如魚得水了。
現在的問題是,保證你一定可以跳到最後一格,請問你最少要跳多少次,才能跳過去。
我們先來說說動態規劃的思路,採用自頂向下的遞迴動態規劃,可以這樣定義乙個dp
函式:
// 定義:從索引 p 跳到最後一格,至少需要 dp(nums, p) 步
int dp(vector& nums, int p);
我們想求的結果就是dp(nums, 0)
,base case 就是當p
超過最後一格時,不需要跳躍:
if (p >= nums.size() - 1)
根據前文 動態規劃套路詳解 的動規框架,就可以暴力窮舉所有可能的跳法,通過備忘錄memo
消除重疊子問題,取其中的最小值最為最終答案:
vectormemo;
// 主函式
int jump(vector& nums)
int dp(vector& nums, int p)
// 子問題已經計算過
if (memo[p] != n)
int steps = nums[p];
// 你可以選擇跳 1 步,2 步...
for (int i = 1; i <= steps; i++)
return memo[p];
}
這個動態規劃應該很明顯了,按照前文 動態規劃套路詳解 所說的套路,狀態就是當前所站立的索引p
,選擇就是可以跳出的步數。
該演算法的時間複雜度是 遞迴深度 × 每次遞迴需要的時間複雜度,即 o(n^2),在 leetcode 上是無法通過所有用例的,會超時。
剛才的動態規劃思路,不是要窮舉所有子問題,然後取其中最小的作為結果嗎?核心的**框架是這樣:
int steps = nums[p];
// 你可以選擇跳 1 步,2 步...
for (int i = 1; i <= steps; i++)
for 迴圈中會陷入遞迴計算子問題,這是動態規劃時間複雜度高的根本原因。
但是,真的需要【遞迴地】計算出每乙個子問題的結果,然後求最值嗎?直觀地想一想,似乎不需要遞迴,只需要判斷哪乙個選擇最具有【潛力】即可:
比如上圖這種情況,我們站在索引 0 的位置,可以向前跳 1,2 或 3 步,你說應該選擇跳多少呢?
顯然應該跳 2 步調到索引 2,因為nums[2]
的可跳躍區域涵蓋了索引區間[3..6]
,比其他的都大。如果想求最少的跳躍次數,那麼往索引 2 跳必然是最優的選擇。
你看,這就是貪心選擇性質,我們不需要【遞迴地】計算出所有選擇的具體結果然後比較求最值,而只需要做出那個最有【潛力】,看起來最優的選擇即可。
繞過這個彎兒來,就可以寫**了:
int jump(vector& nums)
}return jumps;
}
結合剛才那個圖,就知道這段短小精悍的**在幹什麼了:
i
和end
標記了可以選擇的跳躍步數,farthest
標記了所有選擇[i..end]
中能夠跳到的最遠距離,jumps
記錄了跳躍次數。
本演算法的時間複雜度 o(n),空間複雜度 o(1),可以說是非常高效,動態規劃都被吊起來打了。
至此,兩道跳躍問題都使用貪心演算法解決了。
其實對於貪心選擇性質,是可以有嚴格的數學證明的,有興趣的讀者可以參看《演算法導論》第十六章,專門有乙個章節介紹貪心演算法。這裡限於篇幅和通俗性,就不展開了。
使用貪心演算法的實際應用還挺多,比如赫夫曼編碼也是乙個經典的貪心演算法應用。更多時候運用貪心演算法可能不是求最優解,而是求次優解以節約時間,比如經典的旅行商問題。
不過我們常見的貪心演算法題目,就像本文的題目,大多一眼就能看出來,大不了就先用動態規劃求解,如果動態規劃都超時,說明該問題存在貪心選擇性質無疑了。
_____________
超級書架(貪心思想)
題面 from luogu 超級書架 farmer john最近為奶牛們的圖書館添置了乙個巨大的書架,儘管它是如此的大,但它還是幾乎瞬間就被各種各樣的書塞滿了。現在,只有書架的頂上還留有一點空間。所有n 1 n 20,000 頭奶牛都有乙個確定的身高h i 1 h i 10,000 設所有奶牛身高的...
最優合併問題 貪心思想
最優合併問題 description 給定k 個排好序的序列s1 s2,sk 用2 路合併演算法將這k 個序列合併成乙個序列。假設所採用的2 路合併演算法合併2 個長度分別為m和n的序列需要m n 1次比較。試設計乙個演算法確定合併這個序列的最優合併順序,使所需的總比較次數最少。為了進行比較,還需要...
ACM 最少攔截系統 貪心思想
最少攔截系統 time limit 1000ms memory limit 32768k 某國為了防禦敵國的飛彈襲擊,發展出一種飛彈攔截系統。但是這種飛彈攔截系統有乙個缺陷 雖然它的第一發炮彈能夠到達任意的高度,但是以後每一發炮彈都不能超過前一發的高度。某天,雷達捕捉到敵國的飛彈來襲,因為該系統還在...