動態框架方法論

KDLin發表於2021-01-03

動態規劃

20201229

14:06

 

目錄

-

- 經典三概念與解決思想

- 經典的解決思想

- 動態規劃的方法論

- 案例:找零

- 求解框架

- REF

 

 

經典三概念與解決思想

動態規劃是什麼?動態規劃有三個特徵。

  • 重複子問題,這個特點很好理解,遞迴樹是一個非常好方法,它的一個功能就是能直觀地感受到重複子問題
  • 最優子結構,當前問題可以由子問題的solution解決,這個特性,遞迴地使得當前問題和子問題具備相同的結構
  • 狀態轉移方程,這也是最難的一步,既然當前問題可以由子問題解決,那麼轉移公式是?

經典的解決思想

  • 自頂向下,逐步求解
  • 自底向上,反向求精

這是計算機世界一種經典的思想,在動態規劃、遞迴、回溯中,自頂向下的方法通常是非常直接的暴力解法——這非常有用,是一切優化的開始。

動態規劃的方法論

解決這些問題需要找到四個要素:

  • base,求解停止的基礎條件是?到哪種狀態可以直接返回。
  • state,狀態,這是非常關鍵的,它指的是可以代表當前問題的一些狀態量的集合,對應到遞迴樹中,這個狀態就是遞迴樹中的一個節點——這非常重要。
  • select,選擇,改變狀態的行為
  • dp function,dp動態規劃函式,函式一般返回某個狀態的solution,這個也是最難定義的暴力解決方案

以找零錢問題為例,請參考REF中原題

  • base,當amount=0的時候,表示不用湊硬幣了,直接return 0
  • state,狀態,這個問題的關鍵狀態量只有一個,那就是當前問題的amount——這個目標金額,最少需要多少枚硬幣可以湊齊
  • select,改變目標金額的唯一行為是選擇某個硬幣
  • dp函式,根據以上dp函式可以定義為,c(最少需要的硬幣數) = dp(目標金額)

案例:找零

根據上述分析找零問題的暴力求解虛擬碼框架是:

    1 def dp(n):

    2     for coin in coins:

    3         res = min(res, dp(n-coin)+1)

    4     return res

詳細解法,可以參考原文,見ref。

上述框架,加上基值條件,完整的程式碼如下。

    1 def dp(n):

    2     if n == 0: return 0

    3     if n < 0: return -1

    4     res = float('INF')

    5     for coin in coins:

    6         t = dp(n-coin)

    7         if t == -1: continue

    8         res = min(res, t+1)

    9     return res if res != float('INF') else -1

   10 # 備忘錄

   11 memo = {}

   12 def dp(n):

   13     # memo

   14     if n in memo: return memo[n]

   15     # base

   16     if n == 0: return 0

   17     if n < 0: return -1

   18     # init res

   19     res = float('INF')

   20     # chose coin

   21     for coin in coins:

   22         t = dp(n-coin)

   23         # invalid

   24         if t == -1: continue

   25         res = min(res, t+1)

   26     memo[n] = res if res != float('INF') else -1

   27     return memo[n]

   28 # 迭代

   29 memo = [0]

   30

   31 for n in range(1, amount+1):

   32     memo.append(amount+1)

   33     for coin in coins:

   34         # 檢查越界

   35         if n - coin >= 0:

   36             memo[n] = min(memo[n-coin]+1, memo[n])

   37 return memo[n] if memo[n] != (amount+1) else -1

  • 去除重複子問題的最直接的方式就是使用備忘錄記錄狀態
  • 完成了備忘錄方法,那麼最後的問題就是,如何自底向上計算出備忘錄(所有狀態對應的解法的陣列)

程式碼如上。

求解框架

該框架有點令人費解,但是仔細對照找零問題,其中核心問題就是理解“狀態”——狀態備忘錄,記錄的是當前狀態的solution。

    1 # 初始化 base case

    2 dp[0][0][...] = base

    3 # 進行狀態轉移

    4 for 狀態1 in 狀態1的所有取值:

    5     for 狀態2 in 狀態2的所有取值:

    6         for ...

    7             dp[狀態1][狀態2][...] = 求最值(選擇1,選擇2...)

 

REF

 

經典的入門問題是斐波那契數列數列,以及找零錢問題。解法太過常見,這次來談一些正確的思路。

 

相關文章