01背包问题
基础背包
1 2 3 4 5 6 7
| int[] dp = new int[weight+1]; for(int i=1;i<=n;i++){ for(int j=weight;j>=nums[i-1];j--){ dp[j] = Math.max(dp[j-nums[i-1]]+nums[i-1],dp[j]); } } return dp[weight];
|
目标和
leetcode 494
思路:见代码注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| public static int findTargetSumWays4(int[] nums, int target) { int sum = 0; for (int n : nums) { sum += n; } if (sum < target || ((target & 1) ^ (sum & 1)) == 1) { return 0; } return subsets(nums, (target + sum) >> 1); }
public static int subsets(int[] nums, int t) { if (t < 0) { return 0; } int[] dp = new int[t + 1]; dp[0] = 1; for (int num : nums) { for (int j = t; j >= num; j--) { dp[j] += dp[j - num]; } } return dp[t]; }
|
最后一块石头的重量
leetcode 1049
思路:转化为找出一个数组中最接近和的一半的子序列和。答案就是sum - 2 * 子序列和,找最接近和的一半的子序列和就是01背包问题。背包容量为一半,然后找出价值最高的子序列和。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public int lastStoneWeightII(int[] stones) { int sum = 0; for (int stone : stones) { sum+=stone; } int near = findLSW(stones,sum/2); return sum - near - near; }
private int findLSW(int[] stones, int i) { int[] dp = new int[i+1]; for (int stone : stones) { for(int j=i;j>=stone;j--){ dp[j] = Math.max(dp[j],dp[j-stone]+stone); } } return dp[i]; }
|
分组背包
模板题
同一个组的物品只能选一个,求最大价值。
思路:还是分为要跟不要。但是要的情况需要讨论要一组里的哪一个。所以dp[i][j] 中的i表示物品组,j表示背包容量。开始时可以按组号排序,一组一组处理即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| public static int compute1() { int teams = 1; for (int i = 2; i <= n; i++) { if (arr[i - 1][2] != arr[i][2]) { teams++; } } int[][] dp = new int[teams + 1][m + 1]; for (int start = 1, end = 2, i = 1; start <= n; i++) { while (end <= n && arr[end][2] == arr[start][2]) { end++; } for (int j = 0; j <= m; j++) { dp[i][j] = dp[i - 1][j]; for (int k = start; k < end; k++) { if (j - arr[k][0] >= 0) { dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - arr[k][0]] + arr[k][1]); } } } start = end++; } return dp[teams][m]; }
public static int compute2() { Arrays.fill(dp, 0, m + 1, 0); for (int start = 1, end = 2; start <= n;) { while (end <= n && arr[end][2] == arr[start][2]) { end++; } for (int j = m; j >= 0; j--) { for (int k = start; k < end; k++) { if (j - arr[k][0] >= 0) { dp[j] = Math.max(dp[j], arr[k][1] + dp[j - arr[k][0]]); } } } start = end++; } return dp[m]; }
|
从栈中取出k个硬币的最大面值和
leetcode 2218
思路:以一个栈里面的硬币为分组。一组里面的元素就是拿一个硬币,两个硬币,三个硬币…得到的面值。一组我也只能拿一个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
|
public static int maxValueOfCoins1(List<List<Integer>> piles, int m) { int n = piles.size(); int[][] dp = new int[n + 1][m + 1]; for (int i = 1; i <= n; i++) { List<Integer> team = piles.get(i - 1); int t = Math.min(team.size(), m); int[] preSum = new int[t + 1]; for (int j = 0, sum = 0; j < t; j++) { sum += team.get(j); preSum[j + 1] = sum; } for (int j = 0; j <= m; j++) { dp[i][j] = dp[i - 1][j]; for (int k = 1; k <= Math.min(t, j); k++) { dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - k] + preSum[k]); } } } return dp[n][m]; }
public static int maxValueOfCoins2(List<List<Integer>> piles, int m) { int[] dp = new int[m + 1]; for (List<Integer> team : piles) { int t = Math.min(team.size(), m); int[] preSum = new int[t + 1]; for (int j = 0, sum = 0; j < t; j++) { sum += team.get(j); preSum[j + 1] = sum; } for (int j = m; j > 0; j--) { for (int k = 1; k <= Math.min(t, j); k++) { dp[j] = Math.max(dp[j], dp[j - k] + preSum[k]); } } } return dp[m]; }
|
完全背包
模板题
一个物品可以重复取,求最大价值。
dp[i][j] = Max(dp[i-1][j],dp[i][j-w[i]]+v[i]) //拿了之后i也不减一,就可以拿多次。空间压缩需要正着遍历。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public static long compute1() { int[][] dp = new int[m + 1][t + 1]; for (int i = 1; i <= m; i++) { for (int j = 0; j <= t; j++) { dp[i][j] = dp[i - 1][j]; if (j - cost[i] >= 0) { dp[i][j] = Math.max(dp[i][j], dp[i][j - cost[i]] + val[i]); } } } return dp[m][t]; }
public static long compute2() { Arrays.fill(dp, 1, t + 1, 0); for (int i = 1; i <= m; i++) { for (int j = cost[i]; j <= t; j++) { dp[j] = Math.max(dp[j], dp[j - cost[i]] + val[i]); } } return dp[t]; }
|
正则匹配
leetcode 10
思路:在dfs注解里面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| public static boolean isMatch1(String str, String pat) { char[] s = str.toCharArray(); char[] p = pat.toCharArray(); return f1(s, p, 0, 0); }
public static boolean f1(char[] s, char[] p, int i, int j) { if (i == s.length) { if (j == p.length) { return true; } else { return j + 1 < p.length && p[j + 1] == '*' && f1(s, p, i, j + 2); } } else if (j == p.length) { return false; } else { if (j + 1 == p.length || p[j + 1] != '*') { return (s[i] == p[j] || p[j] == '.') && f1(s, p, i + 1, j + 1); } else { boolean p1 = f1(s, p, i, j + 2); boolean p2 = (s[i] == p[j] || p[j] == '.') && f1(s, p, i + 1, j); return p1 || p2; } } }
public boolean isMatch(String s1, String p1) { char[] s = s1.toCharArray(); char[] p = p1.toCharArray(); int n = s.length; int m = p.length; boolean[][] dp = new boolean[n+1][m+1]; dp[n][m] = true; for(int j =m-1;j>=0;j--){ dp[n][j] = j+1 < m && p[j+1] == '*' && dp[n][j+2]; } for(int i = n-1;i>=0;i--){ for(int j = m-1;j>=0;j--){ if(j+1 == m || p[j+1] != '*'){ dp[i][j] = (s[i] == p[j] || p[j] == '.') && dp[i+1][j+1]; }else { dp[i][j] = dp[i][j+2] || ( (s[i] == p[j] || p[j] == '.') && dp[i+1][j]); } } } return dp[0][0]; }
|
通配符匹配
leetcode 44
思路: 还是一样的。先初始化边界条件。然后如果遇到不是’‘ 就正常判断 dp[i][j] = dp[i+1][j+1] && (s[i] == p[j] || p[j] == ‘?’);’ 。遇到就是完全背包问题,可以用匹配一个之后不移动j , 也可以直接跳过 。 dp[i][j] = dp[i][j+1] || dp[i+1][j];
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public boolean isMatch(String s1, String p1) { char[] s = s1.toCharArray(); char[] p = p1.toCharArray(); int n = s.length; int m = p.length; boolean[][] dp = new boolean[n+1][m+1]; dp[n][m] = true; for(int j =m-1;j>=0;j--){ dp[n][j] = p[j] == '*' && dp[n][j+1]; } for(int i =n-1;i>=0;i--){ for(int j =m-1;j>=0;j--){ if(p[j]!='*'){ dp[i][j] = (s[i] == p[j] || p[j] == '?') && dp[i+1][j+1]; }else { dp[i][j] = dp[i][j+1] || dp[i+1][j]; } } } return dp[0][0]; }
|
干草问题
购买足量干草的最小花费
有n个提供干草的公司,每个公司都有两个信息
cost[i]代表购买1次产品需要花的钱
val[i]代表购买1次产品所获得的干草数量
每个公司的产品都可以购买任意次
你一定要至少购买h数量的干草,返回最少要花多少钱
测试链接 : https://www.luogu.com.cn/problem/P2918
思路:还是完全背包但是定义dp。 dp[i][j] 表示1….i公司购买严格等于j份甘草时花费最小。 那么最后只用遍历 h 到 m 找出最小值即可。 m = h + max(val[i])。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| public static int compute1() { int[][] dp = new int[n + 1][m + 1]; Arrays.fill(dp[0], 1, m + 1, Integer.MAX_VALUE); for (int i = 1; i <= n; i++) { for (int j = 0; j <= m; j++) { dp[i][j] = dp[i - 1][j]; if (j - val[i] >= 0 && dp[i][j - val[i]] != Integer.MAX_VALUE) { dp[i][j] = Math.min(dp[i][j], dp[i][j - val[i]] + cost[i]); } } } int ans = Integer.MAX_VALUE; for (int j = h; j <= m; j++) { ans = Math.min(ans, dp[n][j]); } return ans; }
public static int compute2() { Arrays.fill(dp, 1, m + 1, Integer.MAX_VALUE); for (int i = 1; i <= n; i++) { for (int j = val[i]; j <= m; j++) { if (dp[j - val[i]] != Integer.MAX_VALUE) { dp[j] = Math.min(dp[j], dp[j - val[i]] + cost[i]); } } } int ans = Integer.MAX_VALUE; for (int j = h; j <= m; j++) { ans = Math.min(ans, dp[j]); } return ans; }
|
多重背包
多重背包问题:就是有数量限制的完全背包。每个物品有一定数量,不能无限拿。
思路:每到一个物品就枚举拿多少个的情况,求出最大值即可。但这里没用枚举优化可能会超时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
public static int compute1() { int[][] dp = new int[n + 1][t + 1]; for (int i = 1; i <= n; i++) { for (int j = 0; j <= t; j++) { dp[i][j] = dp[i - 1][j]; for (int k = 1; k <= c[i] && w[i] * k <= j; k++) { dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - k * w[i]] + k * v[i]); } } } return dp[n][t]; }
public static int compute2() { for (int i = 1; i <= n; i++) { for (int j = t; j >= 0; j--) { for (int k = 1; k <= c[i] && w[i] * k <= j; k++) { dp[j] = Math.max(dp[j], dp[j - k * w[i]] + k * v[i]); } } } return dp[t]; }
|
二进制分组优化枚举。假如一个货物有13个,那么可以拆分为 1,2,4,6 。然后将这四个数看出四个物品即可。转化为了01背包问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StreamTokenizer in = new StreamTokenizer(br); PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); a=0; while(in.nextToken() != StreamTokenizer.TT_EOF){ n = (int)in.nval; in.nextToken(); W = (int) in.nval; for(int i=1,v,w,m;i<=n;i++){ in.nextToken(); v = (int) in.nval; in.nextToken(); w = (int) in.nval; in.nextToken(); m = (int) in.nval; for(int j = 1; m>=j ; j<<=1){ val[a] = v*j; weight[a++] = w*j; m-=j; } if(m>0){ val[a] = m*v; weight[a++] = w*m; } } out.println(compute()); } out.flush(); out.close(); br.close(); }
public static int compute(){ Arrays.fill(dp,0,W+1,0); for(int i = 0;i<a;i++){ for(int j = W;j>=weight[i];j--){ dp[j] = Math.max(dp[j],dp[j-weight[i]]+val[i]); } } return dp[W]; }
|