九章算法高级班笔记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

特点:

  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.

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.

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

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

来来来, 先画个图:
Evernote Snapshot 20171105 151531 (1)

如图, 我们发现, 一下相同的重复的[2], 可以用记忆花搜索. 但是, 假设我们用一个状态dp[1], 我们不知道剩的一个, 是2, 是3还是4啊. 因为现在取一个硬币,可以从左边取, 也可以从右边取, 是有方向性的, 所以不能用dp[i]表示.
现在我们呢, 用一个区间的两个下标表示

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]; } } 

我们会发现, 如图

Evernote Snapshot 20171105 152835

我们是先初始化对角线, 然后往按区间往右上递推的.先黄色的线, 然后蓝色的线, 然后粉色的…

初始化, 就是初始化那些没法递推的.
从大往小想比较容易想, 可以用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过程就是填写矩阵
  • 可以滚动数组优化

猜你喜欢

转载自www.cnblogs.com/jiuzhangsuanfa/p/9895696.html