九章算法高級班筆記6.動態規劃(下)

  1. 區間類DP
  • Stone Game
  • Burst Ballons
  • Scramble String
  1. 匹配類動規
  • Longest Common Subsequence
  • Edit Distance
  • K Edit Distance
  • Distinct Subquence
  • Interleaving String
  1. 揹包類DP
  • BackPackI
  • BackPackII
  • K SUM
  • Minimum Adjustment Cost

 

區間類Dp 

cs3k.com

特色:html

  1. 求一段區間的解max/min/count
  2. 轉移方程經過區間更新
  3. 從大到小的更新

Coins in a Line III cs3k.com

There are n coins in a line. Two players take turns to take a coin from one of the ends of the line until there are no more coins left. The player with the larger amount of money wins.java

Could you please decide the first player will win or lose?數組

Have you met this question in a real interview? Yes
Example
Given array A = [3,2,2], return true.ide

Given array A = [1,2,4], return true.wordpress

Given array A = [1,20,4], return false.優化

來來來, 先畫個圖:
Evernote Snapshot 20171105 151531 (1)ui

如圖, 咱們發現, 一下相同的重複的[2], 能夠用記憶花搜索. 可是, 假設咱們用一個狀態dp[1], 咱們不知道剩的一個, 是2, 是3仍是4啊. 由於如今取一個硬幣,能夠從左邊取, 也能夠從右邊取, 是有方向性的, 因此不能用dp[i]表示.
如今咱們呢, 用一個區間的兩個下標表示this

public class Solution { /** * @param values: an array of integers * @return: a boolean which equals to true if the first player will win */ public boolean firstWillWin(int[] values) { // write your code here int n = values.length; int[] sum = new int[n + 1]; sum[0] = 0; for (int i = 1; i <= n; ++i) sum[i] = sum[i - 1] + values[i - 1]; // s[i][j] = sum[j + 1] - sum[i]; int[][] dp = new int[n][n]; for (int i = 0; i < n; ++i) dp[i][i] = values[i]; for (int len = 2; len <= n; ++len) { for (int i = 0; i < n; ++i) { int j = i + len - 1; if (j >= n) continue; int s = sum[j + 1] - sum[i]; dp[i][j] = Math.max(s - dp[i + 1][j], s - dp[i][j - 1]); } } return dp[0][n - 1] > sum[n] / 2; } } // 方法一 import java.util.*; public class Solution { /** * @param values: an array of integers * @return: a boolean which equals to true if the first player will win */ public boolean firstWillWin(int[] values) { // write your code here int n = values.length; int [][]dp = new int[n + 1][n + 1]; boolean [][]flag =new boolean[n + 1][n + 1]; int sum = 0; for(int now : values) sum += now; return sum < 2*MemorySearch(0,values.length - 1, dp, flag, values); } int MemorySearch(int left, int right, int [][]dp, boolean [][]flag, int []values) { if(flag[left][right]) return dp[left][right]; flag[left][right] = true; if(left > right) { dp[left][right] = 0; } else if (left == right) { dp[left][right] = values[left]; } else if(left + 1 == right) { dp[left][right] = Math.max(values[left], values[right]); } else { int pick_left = Math.min(MemorySearch(left + 2, right, dp, flag, values), MemorySearch(left + 1, right - 1, dp, flag, values)) + values[left]; int pick_right = Math.min(MemorySearch(left, right - 2, dp, flag, values), MemorySearch(left + 1, right - 1, dp, flag, values)) + values[right]; dp[left][right] = Math.max(pick_left, pick_right); } return dp[left][right]; } } // 方法二 import java.util.*; public class Solution { /** * @param values: an array of integers * @return: a boolean which equals to true if the first player will win */ public boolean firstWillWin(int[] values) { // write your code here int n = values.length; int [][]dp = new int[n + 1][n + 1]; boolean [][]flag =new boolean[n + 1][n + 1]; int[][] sum = new int[n + 1][n + 1]; for (int i = 0; i < n; i++) { for (int j = i; j < n; j++) { sum[i][j] = i == j ? values[j] : sum[i][j-1] + values[j]; } } int allsum = 0; for(int now : values) allsum += now; return allsum < 2*MemorySearch(0,values.length - 1, dp, flag, values, sum); } int MemorySearch(int left, int right, int [][]dp, boolean [][]flag, int []values, int [][]sum) { if(flag[left][right]) return dp[left][right]; flag[left][right] = true; if(left > right) { dp[left][right] = 0; } else if (left == right) { dp[left][right] = values[left]; } else if(left + 1 == right) { dp[left][right] = Math.max(values[left], values[right]); } else { int cur = Math.min(MemorySearch(left+1, right, dp, flag, values, sum), MemorySearch(left,right-1, dp, flag, values, sum)); dp[left][right] = sum[left][right] - cur; } return dp[left][right]; } } 

咱們會發現, 如圖spa

Evernote Snapshot 20171105 152835

咱們是先初始化對角線, 而後往按區間往右上遞推的.先黃色的線, 而後藍色的線, 而後粉色的…3d

初始化, 就是初始化那些無法遞推的.
從大往小想比較容易想, 能夠用for來寫, 可是寫起來沒有遞歸單獨寫出一個方程來容易.

Stone Game 

cs3k.com

There is a stone game. At the beginning of the game the player picks n piles of stones in a line.

The goal is to merge the stones in one pile observing the following rules:

At each step of the game,the player can merge two adjacent piles to a new pile.
The score is the number of stones in the new pile.
You are to determine the minimum of the total score.
Example
For [4, 1, 1, 4], in the best solution, the total score is 18:

  1. Merge second and third piles => [4, 2, 4], score +2
  2. Merge the first two piles => [6, 4],score +6
  3. Merge the last two piles => [10], score +10

Other two examples:
[1, 1, 1, 1] return 8
[4, 4, 5, 9] return 43

不能用貪心

這道題不能用貪心, 就是永遠取剩下兩個數字裏面比較小的那兩個. 如圖:

6    4    4   6               4和4合併  +8
6       8     6               6和8合併  +14
14            6               14和6合併 +20
      20                      sum = 42

可是這是錯的, 由於正確的應該是:

6    4    4   6               6和4合併   +10
  10      4   6               4和6合併   +10
  10        10                10和10合併 +20
      20                      sum = 40

貪心錯誤的緣由是: 合併的條件是相鄰, 而不是隨便挑最小的合併. 若是不要求相鄰, 那纔是對的.

咱們能夠想到, 如圖所示, 枚舉全部可能的狀況

Evernote Snapshot 20171105 154248

搜索是萬能的, 可是這道題用搜索, 就沒有能夠合併的狀態了. 由於從小往大的搜索, 是很難用記憶化搜索作的. 咱們能夠反過來想, 從大往小. 最大的就是要求的, 而後咱們倒着搜:
Evernote Snapshot 20171105 155157
如圖6.4所示, 這樣咱們就發現, 誒有重複的了!! 能夠記憶化搜索啦!記憶化搜索是由大拆小的過程.

死衚衕:容易想到的一個思路從小往大,枚舉第一次合併是在哪?

記憶化搜索的思路,從大到小,先考慮最後的0-N-1 合併的總花費

  1. State:
  • dp[i][j] 表示把第i到第j個石子合併到一塊兒的最小花費
  1. Function:
  • 預處理sum[i,j] 表示i到j全部石子價值和
  • dp[i][j] = min(dp[i][k]+dp[k+1][j]+sum[i,j]) 對於全部k屬於{i,j}
  1. Intialize:
  • for each i
    • dp[i][i] = 0
  1. Answer:
  • dp[0][n-1]
public class Solution { /** * @param A an integer array * @return an integer */ int search(int l, int r, int[][] f, int[][] visit, int[][] sum) { if(visit[l][r] == 1) return f[l][r]; if(l == r) { visit[l][r] = 1; return f[l][r]; } f[l][r] = Integer.MAX_VALUE; for (int k = l; k < r; k++) { f[l][r] = Math.min(f[l][r], search(l, k, f, visit, sum) + search(k + 1, r, f, visit, sum) + sum[l][r]); } visit[l][r] = 1; return f[l][r]; } public int stoneGame(int[] A) { if (A == null || A.length == 0) { return 0; } int n = A.length; // initialize int[][] f = new int[n][n]; int[][] visit = new int[n][n]; for (int i = 0; i < n; i++) { f[i][i] = 0; } // preparation int[][] sum = new int[n][n]; for (int i = 0; i < n; i++) { sum[i][i] = A[i]; for (int j = i + 1; j < n; j++) { sum[i][j] = sum[i][j - 1] + A[j]; } } return search(0, n-1, f, visit, sum); } } // for 循環 public class Solution { /** * @param A an integer array * @return an integer */ public int stoneGame(int[] A) { // Write your code here if(A.length==0) { return 0; } int[][] dp=new int[A.length][A.length]; int[] sums=new int[A.length+1]; sums[0]=0; for(int i=0;i<A.length;i++){ for(int j=i;j<A.length;j++){ dp[i][j]=Integer.MAX_VALUE; } } for(int i=0;i<A.length;i++){ dp[i][i]=0; sums[i+1]=sums[i]+A[i]; } return search(0,A.length-1,dp,sums); } private int search(int start, int end, int[][] dp, int[] sums){ if(dp[start][end]!=Integer.MAX_VALUE){ return dp[start][end]; } int min=Integer.MAX_VALUE; for(int k=start;k<end;k++){ int left = search(start,k,dp,sums); int right = search(k+1,end,dp,sums); int now = sums[end+1]-sums[start]; min=Math.min(min,left+right+now); } dp[start][end]=min; return min; } } 

時間複雜度的話呢, 咱們最好能夠預處理sum, 這樣最後是O(n^3), 其中n^2
是遍歷, 剩下的n是每一個位置的移動.

Burst Balloons

cs3k.com

Given n balloons, indexed from 0 to n-1. Each balloon is painted with a number on it represented by array nums. You are asked to burst all the balloons. If the you burst balloon i you will get nums[left] * nums[i] * nums[right] coins. Here left and right are adjacent indices of i. After the burst, the left and right then becomes adjacent.

Find the maximum coins you can collect by bursting the balloons wisely.

  • You may imagine nums[-1] = nums[n] = 1. They are not real therefore you can not burst them.
  • 0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100

Have you met this question in a real interview? Yes
Example
Given [4, 1, 5, 10]
Return 270

nums = [4, 1, 5, 10] burst 1, get coins 4 * 1 * 5 = 20
nums = [4, 5, 10] burst 5, get coins 4 * 5 * 10 = 200
nums = [4, 10] burst 4, get coins 1 * 4 * 10 = 40
nums = [10] burst 10, get coins 1 * 10 * 1 = 10

Total coins 20 + 200 + 40 + 10 = 270

咱們能夠用圖6.5的方法, 枚舉第一次吹爆哪一個氣球, 而後求解:
Evernote Snapshot 20171105 155243

咱們同時要想到, 能夠從大往小, 枚舉咱們最後打爆哪一個:

Evernote Snapshot 20171105 155653

從而使用記憶化搜索, 記錄區間的最大值. 時間複雜度是O(n^3).
從大到小,先考慮最後的0-n-1 合併的總價值

  1. State:
  • dp[i][j] 表示把第i到第j個氣球打爆的最大價值
  1. Function:
  • 對於全部k屬於{i,j}, 表示第k號氣球最後打爆。
  • midValue = arr[i-1] * arr[k] * arr[j+1];
  • dp[i][j] = max(dp[i][k-1]+dp[k+1][j]+midvalue)
  1. Intialize:
  • for each i
  • dp[i][i] = arr[i-1] * arr[i] * arr[i+1];
  1. Answer:
  • dp[0][n-1]
public class Solution { public int maxCoins(int[] nums) { int n = nums.length; int [][]dp = new int [n+2][n+2]; int [][]visit = new int[n+2][n+2]; int [] arr = new int [n+2]; for (int i = 1; i <= n; i++){ arr[i] = nums[i-1]; } arr[0] = 1; arr[n+1] = 1; return search(arr, dp, visit, 1 , n); } public int search(int []arr, int [][]dp, int [][]visit, int left, int right) { if(visit[left][right] == 1) return dp[left][right]; int res = 0; for (int k = left; k <= right; ++k) { int midValue = arr[left - 1] * arr[k] * arr[right + 1]; int leftValue = search(arr, dp, visit, left, k - 1); int rightValue = search(arr, dp, visit, k + 1, right); res = Math.max(res, leftValue + midValue + rightValue); } visit[left][right] = 1; dp[left][right] = res; return res; } } 

區間類dp:

  1. 這種題目共性就是區間最後求[0,n-1] 這樣一個區間
  2. 逆向思惟分析 從大到小就能迎刃而解
  3. 逆向和分治相似

匹配類動態規劃

cs3k.com

匹配類動態規劃的問題, 每每都是兩個字符串, 兩個數組, 讓他們相等, 改着讓他們相等, 求類似的地方的問題. 祕訣是, 之和左, 上和左上三個狀態有關, 畫矩陣求解便可.

  1. state:
  • f[i][j]表明了第一個sequence的前i個數字/字符,配上第二個sequence的前j個…
  1. function: f[i][j] = 研究第i個和第j個的匹配關係
  2. initialize: f[i][0] 和 f[0][i]
  3. answer: f[n][m] min/max/數目/存在關係

其中:n = s1.length(), m = s2.length()

解題技巧畫矩陣,填寫矩陣

Edit Distance

Given two words word1 and word2, find the minimum number of steps required to convert word1 to word2. (each operation is counted as 1 step.)

You have the following 3 operations permitted on a word:

Insert a character
Delete a character
Replace a character
Have you met this question in a real interview? Yes
Example
Given word1 = 「mart」 and word2 = 「karma」, return 3.

思路即套路:

  1. state: f[i][j]表示A的前i個字符最少要用幾回編輯能夠變成B的前j個字符
  2. function: = f[i][j] = MIN(f[i-1][j]+1, f[i][j-1]+1, f[i-1][j-1]) // A[i – 1] == B[j – 1]
    = MIN(f[i-1][j]+1, f[i][j-1]+1, f[i-1][j-1]+1) // A[i – 1] != B[j – 1]
  3. initialize: f[i][0] = i, f[0][j] = j
  4. answer: f[n][m]

enter image description here

public class Solution { public int minDistance(String word1, String word2) { int n = word1.length(); int m = word2.length(); int[][] dp = new int[n+1][m+1]; for(int i=0; i< m+1; i++){ dp[0][i] = i; } for(int i=0; i<n+1; i++){ dp[i][0] = i; } for(int i = 1; i<n+1; i++){ for(int j=1; j<m+1; j++){ if(word1.charAt(i-1) == word2.charAt(j-1)){ dp[i][j] = dp[i-1][j-1]; }else{ dp[i][j] = 1 + Math.min(dp[i-1][j-1],Math.min(dp[i-1][j],dp[i][j-1])); } } } return dp[n][m]; } } // 動態規劃班版本 public class Solution { /** * @param word1 & word2: Two string. * @return: The minimum number of steps. */ public int minDistance(String word1, String word2) { char[] s1 = word1.toCharArray(); char[] s2 = word2.toCharArray(); int i, j; int m = s1.length; int n = s2.length; int[][] f = new int[m + 1][n + 1]; // commented part is for outputting solution // 'I', 'D', 'R', 'S' //char[][] pi = new char[m + 1][n + 1]; for (i = 0; i <= m; ++i) { for (j = 0; j <= n; ++j) { if (i == 0) { f[i][j] = j; continue; } if (j == 0) { f[i][j] = i; continue; } // i > 0, j > 0 // +1, important! // delete insert replace f[i][j] = Math.min(Math.min(f[i - 1][j], f[i][j - 1]), f[i - 1][j - 1]) + 1; /*if (f[i][j] == f[i - 1][j] + 1) { pi[i][j] = 'D'; } else { if (f[i][j] == f[i][j - 1] + 1) { pi[i][j] = 'I'; } else { pi[i][j] = 'R'; } }*/ if (s1[i - 1] == s2[j - 1]) { f[i][j] = Math.min(f[i][j], f[i - 1][j - 1]); // pi[i][j] = 'S'; } } } /*i = m; j = n; while (i > 0 || j > 0) { if (pi[i][j] == 'D') { System.out.println("Delete A's " + i + "-th letter from A"); --i; continue; } if (pi[i][j] == 'I') { System.out.println("Insert B's " + j + "-th letter of B to A"); --j; continue; } if (pi[i][j] == 'R') { System.out.println("Replace the A's " + i + "-th letter to B's " + j + "-th letter"); --i; --j; continue; } if (pi[i][j] == 'S') { --i; --j; continue; } }*/ return f[m][n]; } } 

這道題正好總結下死衚衕時候能夠怎麼辦, 一下每條均可以試試用來拓展解題思路:

  1. 從大往小看不行 從小往大看試試, 反之依然
  2. 跳躍着不行, 試試有序; 能夠挨個取也能夠換着取,跳着取
  3. 一維不行二維試試, 升維不行降維試試
  4. 一個不行用多個(指針,隊列)
  5. 能夠塞進去對的,也能夠全塞進去再扔出去錯的
  6. 橫的豎的都不行就斜着,左斜不行就右斜
  7. 若是用條件找答案找不到,就拿答案試條件
  8. 直接求答案求不到就間接求相關量,相關量求不到就看看能不能直接試出答案
  9. 能夠從前日後,也能夠從後往前

Longest Common Subsequence

cs3k.com

Given two strings, find the longest common subsequence (LCS).

Your code should return the length of LCS.

What’s the definition of Longest Common Subsequence?

https://en.wikipedia.org/wiki/Longest_common_subsequence_problem
http://baike.baidu.com/view/2020307.htm
Example
For 「ABCD」 and 「EDCA」, the LCS is 「A」 (or 「D」, 「C」), return 1.

For 「ABCD」 and 「EACB」, the LCS is 「AC」, return 2.

求什麼就定義什麼, 每步只考慮結尾最後一個字母, 狀態有左, 上和左上決定:

enter image description here

  1. state: f[i][j]表示前i個字符配上前j個字符的LCS的長度
  2. function:
    f[i][j] 
            = MAX(f[i-1][j], f[i][j-1], f[i-1][j-1] + 1) // A[i - 1] == B[j - 1]
            = MAX(f[i-1][j], f[i][j-1])// A[i - 1] != B[j - 1]
  3. intialize: f[i][0] = 0 f[0][j] = 0
  4. answer: f[n][m]
public class Solution { /** * @param A, B: Two strings. * @return: The length of longest common subsequence of A and B. */ public int longestCommonSubsequence(String A, String B) { int n = A.length(); int m = B.length(); int f[][] = new int[n + 1][m + 1]; for(int i = 1; i <= n; i++){ for(int j = 1; j <= m; j++){ f[i][j] = Math.max(f[i - 1][j], f[i][j - 1]); if(A.charAt(i - 1) == B.charAt(j - 1)) f[i][j] = f[i - 1][j - 1] + 1; } } return f[n][m]; } } 

揹包類Dp

cs3k.com

特色:

  1. 用值做爲DP維度
  2. Dp過程就是填寫矩陣
  3. 能夠滾動數組優化

Backpack

Given n items with size Ai, an integer m denotes the size of a backpack. How full you can fill this backpack?

Notice

You can not divide any item into small pieces.

Example
If we have 4 items with size [2, 3, 5, 7], the backpack size is 11, we can select [2, 3, 5], so that the max size we can fill this backpack is 10. If the backpack size is 12. we can select [2, 3, 7] so that we can fulfill the backpack.

You function should return the max size we can fill in the given backpack.

enter image description here

這道題的祕訣是, 雖然是前3個物品, 可是咱們其實不考慮第一個和第二個, 只須要考慮第三個.

好比

dp[1][2]有兩種狀況:
   1. 取     dp[0][0]
   2. 不取   dp[0][2]
   
   dp[3][5]有兩種狀況:
   3. 取     dp[2][0]
   4. 不取   dp[2][5]
  1. State:
    • f[i][S] 「前i」個物品,取出一些可否組成和爲S
  2. Function:
    • a[i-1] 是第i個物品下標是i-1
    • f[i][S] = f[i-1][S – a[i-1]] or f[i-1][S]
  3. Intialize:
    • f[i][0] = true; f[0][1…target] = false
  4. Answer:
    • 檢查全部的f[n][j]

空間複雜度是O(nS) , 滾動數組優化爲O(2S)
時間複雜度是O(n
S)

public class Solution { /** * @param m: An integer m denotes the size of a backpack * @param A: Given n items with size A[i] * @return: The maximum size */ public int backPack(int m, int[] A) { boolean f[][] = new boolean[A.length + 1][m + 1]; for (int i = 0; i <= A.length; i++) { for (int j = 0; j <= m; j++) { f[i][j] = false; } } f[0][0] = true; for (int i = 1; i <= A.length; i++) { for (int j = 0; j <= m; j++) { f[i][j] = f[i - 1][j]; if (j >= A[i-1] && f[i-1][j - A[i-1]]) { f[i][j] = true; } } // for j } // for i for (int i = m; i >= 0; i--) { if (f[A.length][i]) { return i; } } return 0; } } // O(m) 空間複雜度的解法 public class Solution { /** * @param m: An integer m denotes the size of a backpack * @param A: Given n items with size A[i] * @return: The maximum size */ public int backPack(int m, int[] A) { int f[] = new int[m + 1]; for (int i = 0; i < A.length; i++) { for (int j = m; j >= A[i]; j--) { f[j] = Math.max(f[j], f[j - A[i]] + A[i]); } } return f[m]; } } 
 

Backpack IV

Given n items with size nums[i] which an integer array and all positive numbers, no duplicates. An integer target denotes the size of a backpack. Find the number of possible fill the backpack.

Each item may be chosen unlimited number of times

Example
Given candidate items [2,3,6,7] and target 7,

A solution set is:
[7]
[2, 2, 3]
return 2

以前遇到的揹包問題都是0-1揹包問題, 但這道題每一個物品能夠無限次取, 是個無限揹包問題.

f[i][S]是前i個物品, 能組成S的個數.

枚舉雖然能夠取無數個, 可是取到比揹包大就不用取了.

  1. State:
  • f[i][S] 「前i」個物品,取出一些可否組成和爲S
  1. Function:
  • a[i-1] 是第i個物品下標是i-1
  • k 是第i個物品選取的次數
  • f[i][S] = f[i-1][S – k*a[i-1]] or f[i-1][S]
  1. Intialize:
  • f[i][0] = true; f[0][1…target] = false
  1. Answer:
  • 答案是f[n][S]
public class Solution { /** * @param nums an integer array and all positive numbers, no duplicates * @param target an integer * @return an integer */ public int backPackIV(int[] nums, int target) { // Write your code here int m = target; int []A = nums; int f[][] = new int[A.length + 1][m + 1]; f[0][0] = 1; for (int i = 1; i <= A.length; i++) { for (int j = 0; j <= m; j++) { int k = 0; while(k * A[i-1] <= j) { f[i][j] += f[i-1][j-A[i-1]*k]; k+=1; } } // for j } // for i return f[A.length][target]; } } // 方法二 public class Solution { /** * @param nums an integer array and all positive numbers, no duplicates * @param target an integer * @return an integer */ public int backPackIV(int[] nums, int target) { // Write your code here int[] f = new int[target + 1]; f[0] = 1; for (int i = 0; i < nums.length; ++i) for (int j = nums[i]; j <= target; ++j) f[j] += f[j - nums[i]]; return f[target]; } } 

時間是O(nmm) 空間是O(m*n)

Backpack II

Given n items with size Ai and value Vi, and a backpack with size m. What’s the maximum value can you put into the backpack?

Notice

You cannot divide item into small pieces and the total size of items you choose should smaller or equal to m.

Example
Given 4 items with size [2, 3, 5, 7] and value [1, 5, 2, 4], and a backpack with size 10. The maximum value is 9.

這個是揹包問題的馬甲問題.

不能先排序, 而後從大往小裝, 貪心是不對的, 好比揹包容量是9:

A=[4,5,7] V=[3,4,6]

按性價比排序,能夠獲得:

性價比爲:3/4 , 4/5和6/7   取6/7的,體積爲7, 以後不能再加,獲得值爲6.

正確的解法是:取A爲4和5的,最後價值爲7.

那咱們能夠用f[i][j][k]來表示從前i個選體積爲j價值爲k的東西呢?

不須要, 由於價值無上界, 可是體積有。 咱們只須要求知足體積的最大所價值就好了。

  • 時間複雜度O(n*s) , 空間複雜度O(n*m), 空間能夠用滾動數組優化模2
  • 揹包問題只能解整數狀況,若是是小數, 不能作揹包

enter image description here

  1. 狀態 State
  • f[i][j] 表示前i個物品當中選一些物品組成容量爲j的最大價值
  1. 方程 Function
  • f[i][j] = max(f[i-1][j], f[i-1][j-A[i-1]] + V[i-1]);
  1. 初始化 Intialization
  • f[0][0]=0;
  1. 答案 Answer
  • f[n][s]
public class Solution { /** * @param m: An integer m denotes the size of a backpack * @param A & V: Given n items with size A[i] and value V[i] * @return: The maximum value */ public int backPackII(int m, int[] A, int V[]) { // write your code here int[][] dp = new int[A.length + 1][m + 1]; for(int i = 0; i <= A.length; i++){ for(int j = 0; j <= m; j++){ if(i == 0 || j == 0){ dp[i][j] = 0; } else if(A[i-1] > j){ dp[i][j] = dp[(i-1)][j]; } else{ dp[i][j] = Math.max(dp[(i-1)][j], dp[(i-1)][j-A[i-1]] + V[i-1]); } } } return dp[A.length][m]; } } // 方法二 public class Solution { /** * @param m: An integer m denotes the size of a backpack * @param A & V: Given n items with size A[i] and value V[i] */ public int backPackII(int m, int[] A, int V[]) { // write your code here int[] f = new int[m+1]; for (int i = 0; i <=m ; ++i) f[i] = 0; int n = A.length , i, j; for(i = 0; i < n; i++){ for(j = m; j >= A[i]; j--){ if (f[j] < f[j - A[i]] + V[i]) f[j] = f[j - A[i]] + V[i]; } } return f[m]; } }

 

k Sum 

cs3k.com

 

Given n distinct positive integers, integer k (k <= n) and a number target.

Find k numbers where sum is target. Calculate how many solutions there are?

Example

Given [1,2,3,4], k = 2, target = 5.

There are 2 solutions: [1,4] and [2,3].

Return 2.

 

這道題就是構建f[i][j][t]數組, 前i個物品裏面選一些, 和爲j的個數,當前選了t個元素。

  1. State:
    • f[i][j][t]i個數取j個數出來可否和t
  2.  Function:
    • f[i][j][t] = f[i – 1][j – 1][t – a[i-1]] + f[i – 1][j][t]
  3. Intialization
    • f[i][0][0] = 1
  4. Answer
    • f[n][k][target]

時間O(nktarget) 空間O(2(k+1)target)

public class Solution { /** * @param A: an integer array. * @param k: a positive integer (k <= length(A)) * @param target: a integer * @return an integer */ public int kSum(int A[], int k, int target) { int n = A.length; int[][][] f = new int[n + 1][k + 1][target + 1]; for (int i = 0; i < n + 1; i++) { f[i][0][0] = 1; } for (int i = 1; i <= n; i++) { for (int j = 1; j <= k && j <= i; j++) { for (int t = 1; t <= target; t++) { f[i][j][t] = 0; if (t >= A[i - 1]) { f[i][j][t] = f[i - 1][j - 1][t - A[i - 1]]; } f[i][j][t] += f[i - 1][j][t]; } // for t } // for j } // for i return f[n][k][target]; } } 

 

 

DP下總結:

cs3k.com

  1. 區間類DP問題
  • 從大到小去思考
  • 主要是經過記憶化來直觀理解DP的思路
  1. 雙序列類DP問題
  • 二維數組
  • 畫出矩陣的表格,填寫矩陣
  1. 揹包DP問題
  • 用值做爲DP維度
  • DP過程就是填寫矩陣
  • 能夠滾動數組優化
相關文章
相關標籤/搜索