Dynamic Programming 核心想法與模板 | Algorithm in Leetcode

dynamic programming

DP 寫程式不難,難的是式子如何推導,解 DP 題目時,需先定義這三件事:

  • State:dp[i] 代表什麼?
  • Transition:目前狀態如何由較小的子問題得到?常見思路是考慮「選」或「不選」。
  • Base case:最小、最初的狀態是什麼?
程式邏輯大概長這樣
vector<int> dp(n,0); // 初始化
dp[0]=1; // base case
for(int i=2; i<=n; i++){
    dp[i]=dp[i-1]+dp[i-2]; // transition: 依序從之前的答案更新 dp[i]
}
return dp[n];

基礎與選擇 DP

70. Climbing Stairs

這裡我們把 dp[i] 定義為走到第 i 階的方法數,並且我們可以知道走到 i 階的方法有兩種:從 i-1 走 1 階,或從 i-2 走 2 階,所以可以得知 dp[i] = dp[i-1] + dp[i-2]。

746. Min Cost Climbing Stairs

定義dp[i] 為到達第 i 階的最低花費,可以從前一階或前兩階的花費推算。


遇到相鄰位置不能同時選的限制,可比較「選目前項目」和「跳過目前項目」。

198. House Robber

dp[i]代表從 0~i 間房子能獲得的最大收益,如果不搶第 i 間房子,沿用 dp[i-1] 的答案;如果搶第 i 間房子,表示 i-1 不能搶,所以沿用 dp[i-2] 的答案,再加上第 i 間房子的收益。

213. House Robber II

做兩次dp,第一次從房子 0 到 n-2,第二次從房子 1 到 n-1。

337. House Robber III

樹上相鄰節點不能同時選。每個節點回傳兩個值:選它的最高收益(只能加左右子樹「不選」的結果),以及不選它的最高收益(左右子樹各自取較大值);根節點取兩者較大值。

740. Delete and Earn

先統計每個數值出現次數,選數值 x 可得 x * count[x] 分;因為選了 x 就不能選 x-1 和 x+1,所以其實和 House Robber 一樣。

45. Jump Game II

這題可以用 DP 或 Greedy 解,DP 定義 dp[i] 為到位置 i 的最少步數,可由所有能跳到 i 的位置轉移。

背包問題

0/1 背包

每個物品最多選一次,一維陣列由大到小更新。

416. Partition Equal Subset Sum

問題先轉成能否從數字中選出總和為 sum / 2 的子集合。令 dp[i] 表示是否能湊出總和 i,考慮每個數字的影響。

494. Target Sum

把數字分成加號集合 P 和減號集合 N,則 sum(P) - sum(N) = target,可把問題轉成找子集合總和 (sum + target) / 2 的方法數。

完全背包

每個物品可以重複選,一維陣列由小到大更新。

322. Coin Change

令 dp[i] 為湊出金額 i 所需的最少硬幣數,,考慮每個硬幣的影響。

518. Coin Change II

令 dp[i] 為湊出金額 i 的組合數。

Grid DP

62. Unique Paths

只能向右或向下,因此到每格的路徑數為上方與左方路徑數之和:dp[r][c] = dp[r-1][c] + dp[r][c-1]。

63. Unique Paths II

遇到障礙格就令 dp[r][c] = 0。

64. Minimum Path Sum

令 dp[r][c] 為走到該格的最低總和。

字串與序列 DP

300. Longest Increasing Subsequence

令 dp[i] 為以 nums[i] 結尾的最長遞增子序列長度;檢查所有 j < i 且 nums[j] < nums[i],取 dp[j] + 1 的最大值。這個 DP 是 O(n²),也有 O(n log n) 的二分搜解法。

1143. Longest Common Subsequence

令 dp[i][j] 為兩字串前 i、前 j 個字元的 LCS 長度。若最後字元相同,就從左上狀態+1;不同則取上面、左邊兩種結果的最大值。

72. Edit Distance

令 dp[i][j] 為把一字串前綴改成另一字串前綴所需的最少操作數。字元相同時沿用左上值;不同時考慮插入/刪除(左/上)、替換(左上),取三者最小值再加一。

139. Word Break

令 dp[i] 表示字串前 i 個字元能否被字典切分。枚舉任一段起點 j;若 dp[j-1] 為 true 且 s[j..i] 在字典中,則 dp[i] = true。

目錄