0-1背包问题
分为最优解和最优解和两种类型。1、组合问题。2、True、False问题。3、最大最小问题。
最优解一般模板为dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]),
空间压缩后为dp[j]=max(dp[j],dp[j-w[i]]+v[i])。
v[i]与边界的设置,根据题目要求进行更改。
最优解:dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i])
L1049最后一块石头的重量 II与416方法一样,但是转化思路不容易想到
L474 一和零首先1,0的总和只要大于=即可,这里就可以继承了dp[i][j][k] = dp[i - 1][j][k],同时要求max,这里每次循环的时候都要进行比较
最优解和: dp[i] += dp[i-num]
L494目标和,需要掌握一维,二维的转换方法,这里是累加,不再是比较极值了
L879. 盈利计划这里涉及了三维dp,同时又有很多限制条件
组合问题:(并不是0-1背包类型)需要考虑组合顺序,需将target放在外循环,将nums放在内循环
L377组合总和 Ⅳ这里的循环形式是先target放在外面
true or false问题: dp[i] = dp[i] || dp[i-num]
L416分割等和子集包含了整个的推导过程,从二维dp到一维,明白为什么可以照抄
完全背包问题:
需要先了解推导过程,再结合相关的例题,记牢!
L322零钱兑换完全背包问题,这里要求的是价值最小,min类型
L518零钱兑换 II这里包含了完全背包的推导过程,累加过程,
该题注意对比
- L39,组合总和 L40-回溯法
- L377组合总和 Ⅳ
这道题,第一眼看过去和「力扣」 第 377 题:组合总和 Ⅳ 很像,它们的区别在于 结果集是否在乎排列顺序:
「力扣」 第 377 题:一个组合的不同排列是一个新的组合。[1, 1, 2]、[1, 2, 2]、[2, 1, 2] 视为为不同的组合。
「力扣」 第 518 题:一个组合的不同排列在结果集中只出现一次,这一点是「背包问题」的特征,拿东西的顺序不重要。[2, 2, 1] 是一个组合,[1, 2, 2] 和 [2, 1, 2] 不是新的组合。这道题和「力扣」第 39 题:组合总和 很像,只不过:
第 39 题问的是所有的组合列表,应该使用 回溯算法 求解;
第 518 题问的是组合有多少种,而不是问具体的解。应该使用 动态规划 求解。
max类型
关键形式不同的原因在于,一个是累加的求总和的,一个则是求各种可能情况中的最大或者最小值
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读第 1 行
int N = scanner.nextInt();
int V = scanner.nextInt();
// 读后面的体积和价值,不超出体积的情况下取得最大价值
int[] weight = new int[N];
int[] value = new int[N];
for (int i = 0; i < N; i++) {
weight[i] = scanner.nextInt();
value[i] = scanner.nextInt();
}
//初始化第一行
int[][] dp = new int[N][V + 1];
// 因为包含价值为 0 的计算,所以 + 1
for(int i= 0; i * weight[0] <= V; i++){
dp[0][i * weight[0]] = i * value[0];
}
//核心计算
for(int i= 1; i < N; i++){//个数
for(int j = 0; j <= V; j++){//总体积
// 多一个 for 循环,枚举下标为 i 的物品可以选的个数
for(int k = 0; k *weight[i] <= j; k++){
dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - k * weight[i]] + k * value[i]);
}
}
}
System.out.println(dp[N-1][V]);
}
}
上述代码是求体积一定,不超出规定的体积,获得的最高的价值
可进一步精简推导:
其实,重点记忆框住的部分即可
0-1背包倒序是为了参考上一行,因为前面的还没有被修改,可当做上一行
完全背包参考本行,只能先把本行中前面的确定了,才能确定后面的值
//三重循环进行优化
int[][] dp = new int[N + 1][V + 1];
//这里不再进行初始化,由于都+1
for(int i = 1; i <=N; i++){
for(int j = weight[i - 1]; j <= V; j++){
dp[i][j] = Math.max(dp[i][j], dp[i][j - weight[i - 1]] + value[i - 1]);
}
}
System.out.println(dp[N][V]);
再进行空间的优化:
//空间优化
int[] dp = new int[V + 1];
for(int i = 1; i <= N; i++){
for(int j =weight[i - 1]; j <= N; j++){
//从头正序遍历
dp[j] = Math.max(dp[j], dp[j - weight[i - 1]] + value[i - 1]);
}
}
return dp[V];
累加类型
L518零钱兑换 II这里包含了完全背包的推导过程,累加过程
给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。
示例 1:
输入: amount = 5, coins = [1, 2, 5]
输出: 4
解释: 有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
推导过程如下;
(5)即为最终推导的结果,具体过程请看L518零钱兑换 II