每一个动态规划都是从一个网格开始的。
动态规划主要解决的问题是:求最值 主要的核心思想是:穷举 动态规划特点 1.重叠子问题 2.状态转移方程 3.最优子结构 解题的思路: 明确状态 明确选择 明确dp数组/函数的定义
记忆化搜索
动态规划之01背包问题
问题描述:给定 n 件物品,物品的重量为 w[i],物品的价值为 c[i]。现挑选物品放入背包中,假定背包能承受的最大重量为 V,问应该如何选择装入背包中的物品,使得装入背包中物品的总价值最大?
例题描述:
给定 3 件物品,物品的重量为 weight[]={1,3,1},对应的价值为 value[]={15,30,20}。现挑选物品放入背包中,假定背包能承受的最大重量 W 为 4,问应该如何选择装入背包中的物品,使得装入背包中物品的总价值最大?
设置一个dp[w] [n]数组,横坐标为背包的容量,纵坐标为物品的个数。
对于编号为 i 的物品:
dp[i][k] 的结果取两者的较大值,即:
dp[i][k] = max(value[i] + dp[i-1][k-weight[i]], dp[i-1][k])
代码实现如下:
// 横坐标是容量,纵坐标是几号物品。 public int maxValue(int[] weight, int[] value, int W) { int n = weight.length; if (n == 0) return 0; int[][] dp = new int[n][W + 1]; // 先初始化第 0 行,也就是尝试把 0 号物品放入容量为 k 的背包中 for (int k = 1; k <= W; k++) { if (k >= weight[0]) dp[0][k] = value[0]; else dp[0][k] = 0; } for (int i = 1; i < n; i++) { for (int k = 1; k <= W; k++) { // 存放 i 号物品(前提是放得下这件物品) int valueWith_i = (k-weight[i] >= 0) ? (value[i] + dp[i-1][k-weight[i]]) : 0; // 不存放 i 号物品 int valueWithout_i = dp[i-1][k]; dp[i][k] = Math.max(valueWith_i, valueWithout_i); } } return dp[n-1][W]; }
观察上面的代码,会发现,当更新dp[i] [..]时,只与dp[i-1] [..]有关,也就是说,我们没有必要使用O(n*W)的空间,而是只使用O(W)的空间即可。
public int maxValue(int[] weight, int[] value, int W) { int n = weight.length; if (n == 0) return 0; int[] dp = new int[W + 1]; for (int i = 0; i < n; i++) { //只要确保 k>=weight[i] 即可,而不是 k>=1,从而减少遍历的次数 for (int k = W; k >= weight[i]; k--) { dp[k] = Math.max(dp[k - weight[i]] + value[i], dp[k]); } } return dp[W]; }
「力扣」第 416 题:分割等和子集(中等);
「力扣」第 474 题:一和零(中等);
「力扣」第 494 题:目标和(中等);
「力扣」第 879 题:盈利计划(困难);
有n种物品,每种物品有无限个,每个物品的重量为weight[i],每个物品的价值为value[i]。现在有一个背包,它所能容纳的重量为total,问:当你面对这么多有价值的物品时,你的背包所能带走的最大价值是多少?
为什么不能使用贪心算法进行每次选取最高的价格的?
想要举反例很简单,比如只有两个物品:物品A:价值5,体积5,物品B:价值8:体积7,背包容量为10,物品B的性价比显然要比物品A高,那么用贪心算法必然会选择放入一个物品B,此时,剩余的空间已无法装下A或者B,所以得到的最高价值为8,而实际上,选择放入两个物品A即可得到更高的价值10。所以这里贪心算法并不适用。
状态转移方程
dp[i+1] [j] = Math.max(dp[i+1] [j], dp[i] [j-k * V[i]] + k * P[i]);
横坐标 j 是容量的大小,纵坐标 i 是物品的序号,dp[i] [j] 保存的是当前的最大价值。
private int[][] dp = new int[P.length + 1][T + 1]; public void solve() { for (int i = 0; i < P.length; i++){ for (int j = 0; j <= T; j++){ for (int k = 0; k * V[i] <= j; k++){ dp[i+1][j] = Math.max(dp[i+1][j], dp[i][j-k * V[i]] + k * P[i]); } } } System.out.println("最大价值为:" + dp[P.length][T]); } }
状态压缩
private int[] newResults = new int[T+1]; private int ksp(int i, int t){ // 开始填表 for (int m = 0; m < i; m++){ for (int n = V[m]; n <= t; n++){ newResults[n] = Math.max(newResults[n] , newResults[n - V[m]] + P[m]); } } return newResults[newResults.length - 1]; }
「力扣」上的 完全背包问题:
多重背包问题
有n种物品,每种物品有amount[i]个,每个物品的重量为weight[i],每个物品的价值为value[i]。现在有一个背包,它所能容纳的重量为total,问:当你面对这么多有价值的物品时,你的背包所能带走的最大价值是多少?