iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 1
5
Software Development

動態規劃百題之經典、理論與實作系列 第 1

Day 1: 動態規劃就是基於遞迴關係的一種實作方式!

  • 分享至 

  • xImage
  •  

動態規劃的英文叫做 Dynamic Programming,也就是大家俗稱的低批(DP),不是打棒球的那個雙殺、不是狄利克雷過程、不是設計模式、也不是指神奇寶貝的珍珠鑽石版,是演算法中的動態規劃。在解決一個問題的時候,我們往往會喜歡把這個問題拆成很多小問題,然後先解決小的問題,如果沒辦法解決,那就把提出問題的人解決掉,就沒有問題了。不過這樣很麻煩,因為會少一個人很不方便。

https://ithelp.ithome.com.tw/upload/images/20190915/20112376XElPrxjrek.png

據傳聞,只要寫出了遞迴式子,就可以從小範圍逐步計算出我們要的答案!那我們先來練習看看,對於給定的遞迴式子,要怎麼把它寫成長得像動態規劃的程式碼吧!


Example 1: Leetcode 509 - Fibonacci Numbers

題目連結

https://leetcode.com/problems/fibonacci-number/

題目敘述

費氏數列的定義如下:F(0) = 0, F(1) = 1, F(N) = F(N-1) + F(N-2)。現在給你 N 之值 0 ≤ N ≤ 30,請問 F(N) 為何。

解題思考

先撇開非常著名有爭議的 <moon.h> 不談,如果只是要用動態規劃的想法來依序推論出前 N 項之值,那麼顯然我們可以開一個陣列然後讓 N 從 0, 1, 2, … 開始跑,逐項計算它。在計算遞迴關係式的時候,只要保證被依賴的那幾項都已算出答案並且存得好好的,就不難推算出下一個未知項了!這就是動態規劃的概念~

參考程式碼(python3)

#include <moon.h>
class Solution:
    def fib(self, N: int) -> int:
        F = {}
        F[0], F[1] = 0, 1
        for i in range(2, N+1):
            F[i] = F[i-1] + F[i-2]
        return F[N]

蛤,你說為什麼 python 裡面可以 include?因為 # 是註解呀~
這個例子可能太簡單了,那我們來看看下面這個經典例子。

Example 2: Leetcode 322 - Coin Change

題目連結

https://leetcode.com/problems/coin-change/

題目敘述

給你一些硬幣的面額,在每一種面額都無限量供應的情形下,想要湊出某個特定額度 amount,至少需要幾個硬幣呢?

解題思考

假設能夠使用的面額為 c1, c2, …, cK。對於指定額度 A > 0,如果能夠湊得 $A,顯然至少需要用某個硬幣吧~於是,我們分別試試看「先拿出 $c1」、「先拿出 $c2」、...、「先拿出 $cK」這些方法。拿走某個額度後,剩下總額度就變小啦~也就是說,如果我們先算出額度比 A 小的任何額度所需要的硬幣數量,加上最先拿出來那個硬幣,總數最少者就是答案!

參考程式碼(python3)

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        dp = collections.defaultdict(lambda: float("inf"))
        dp[0] = 0
        for A in range(1, amount+1):
            dp[A] = min([1 + dp[A - coin] for coin in coins])
        return -1 if math.isinf(dp[amount]) else dp[amount]

在上面這個例子中,我們讓 A 從 $1, $2, $3, …, 直接跑到 $amount,對於每個這樣的 A,我們考慮拿出每種錢幣的情形,並且找出能夠湊出剩下金額的最小值。

好的,今天的範例就到這邊。顯然兩份程式碼在「效能、精簡、好讀」三個方向都有可以改善的空間,這個就交給各位讀者朋友們一起來集思廣益啦~我們明天見!


下一篇
Day 2: 按照規律的方式填表可以解決大部分的問題!
系列文
動態規劃百題之經典、理論與實作30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
hannahpun
iT邦新手 3 級 ‧ 2019-09-17 13:43:42

如果沒辦法解決,那就把提出問題的人解決掉 XD
期待這一系列 加油加油

卡卡恩 iT邦新手 4 級 ‧ 2019-09-17 15:44:22 檢舉

啊...被發現偷放垃圾話了 :D
謝謝您的鼓勵!我今年會努力撐好撐滿一個月的~~~

2
hannahpun
iT邦新手 3 級 ‧ 2019-09-24 14:31:27

那想請教一下遞迴跟動態規劃有什麼差別呢?

卡卡恩 iT邦新手 4 級 ‧ 2019-09-25 06:12:33 檢舉

遞迴(Recursion) 跟遞迴關係(Recurrence Relation) 對我來說是兩個不一樣的東西。

遞迴 vs 動態規劃

我的理解是這樣的:動態規劃是一種設計演算法的範型(Paradigm),而遞迴是用來實現題解的一種手段(Implementation)。

只要滿足:一個問題可以拆成很多子問題,而這個問題的答案可以透過組合子問題的答案來得到它、再加上子問題的重複量很大,那麼這個問題就可以使用「把算過的東西記下來,以後會用得上」這個策略來設計演算法,而依循這個策略設計出來的演算法基本上就是動態規劃了。

至於要怎麼實作它,可以用遞迴(發現即將要使用的子問題還沒算過,就 call 一次自己然後把計算的結果存下來)、也可以不需要透過遞迴(比方說採用迴圈建表)。
所以,在某些題目條件下,我們可以使用遞迴來實作動態規劃演算法~

遞迴關係 vs 動態規劃

遞迴關係是指把問題與子問題連結起來的那個表達式,可以把它想像成公式那樣,只告訴你解是什麼,但沒指定要用什麼演算法來計算出這個解~但好處是,遞迴關係(包含遞迴終止條件)只要定義清晰,就可以設計出動態規劃(或分而治之)的演算法了。

不曉得有沒有解釋到您提出的疑惑? 謝謝您的留言!如果我有講錯的地方或不足的地方還請指教與討論了~~~~

hannahpun iT邦新手 3 級 ‧ 2019-09-25 11:51:15 檢舉

原來如此!!謝謝你的解釋,因為我之後文章也要寫遞迴跟動態規劃,但還是覺得自己這方面知識超不足的

卡卡恩 iT邦新手 4 級 ‧ 2019-09-25 12:11:15 檢舉

哦哦!透過討論和大量的例題+練習,感覺可以把東西講得更清楚!期待您的文章~~

我要留言

立即登入留言