01背包问题 二维
思路: 动规五部曲
1.确定dp数组以及下标的含义
dp[i][j]表示前i个物品(从0 ~ i)中任意取放入容量为j的背包中,价值总和最大
2.确定递推公式
不放物品i:由dp[i - 1][j]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]就是dp[i - 1][j]。(其实就是当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以被背包内的价值依然和前面相同。)
放物品i:由dp[i - 1][j - weight[i]]推出,dp[i - 1][j - weight[i]] 为背包容量为j - weight[i]的时候不放物品i的最大价值,那么dp[i - 1][j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值。
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])
3.dp数组如何初始化
当背包容量为0时,价值肯定是0,所以dp[i][0] = 0
当i = 0,存放物品序号为0时,如果j < weigth[0], dp[0][j] = 0, 否则dp[0][j] = value[0]
4.确定遍历顺序
依赖决定遍历顺序 由表格可得,dp[i][j]的值依赖于其左上方的值。因此先遍历物品还是先遍历背包并不影响。
5.举例推导dp数组
代码:
public static void main(String[] args) {
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
int bagsize = 4;
testweightbagproblem(weight, value, bagsize);
}
public static void testweightbagproblem(int[] weight, int[] value, int bagsize){
int wlen = weight.length, value0 = 0;
//定义dp数组:dp[i][j]表示背包容量为j时,前i个物品能获得的最大价值
int[][] dp = new int[wlen + 1][bagsize + 1];
//初始化:背包容量为0时,能获得的价值都为0
for (int i = 0; i <= wlen; i++){
dp[i][0] = value0;
}
//遍历顺序:先遍历物品,再遍历背包容量
for (int i = 1; i <= wlen; i++){
for (int j = 1; j <= bagsize; j++){
if (j < weight[i - 1]){
dp[i][j] = dp[i - 1][j];
}else{
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i - 1]] + value[i - 1]);
}
}
}
//打印dp数组
for (int i = 0; i <= wlen; i++){
for (int j = 0; j <= bagsize; j++){
System.out.print(dp[i][j] + " ");
}
System.out.print("\n");
}
}
01背包问题 一维
思路: 1.确定dp数组以及下标的含义
dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]。
2.确定递推公式
dp[j]有两个选择,一个是取自己dp[j] 相当于 二维dp数组中的dp[i-1][j],即不放物品i
一个是取dp[j - weight[i]] + value[i],即放物品i
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
3.dp数组如何初始化
初始化为0
4.确定遍历顺序
倒序遍历是为了保证物品i只被放入一次
这里倒序保证物品只放了一次的是,在二维数组放入物品i前的背包状态处于[i - 1][j - weight[i]],这里是肯定没有放入物品i的,也就是保留了没有放入物品i,容量为j - weight[i]的状态。
但是压缩为一维数组后,若正序遍历j,放入物品i前的背包状态[j - weight[i]] 会经过更新,在本题即状态更新为 放入了物品i 或 没有放入物品i 二者其一。若更新为 放入物品i 则之后状态更新会将此背包状态当作没有放入物品i的状态进行更新,在本题更新体现为 又一次放入了物品i。
若倒序j,状态方程中使用的前一个状态[j - weight[i]] 肯定在[j]前,也就是在物品i本轮遍历j,状态方程所使用的状态[j - weight[i]] (即没有放入物品i的状态,在二维数组中体现为状态[i - 1][j - weight[i]]) 肯定不会在本轮更新为放入物品i的状态(即二维数组中的[i][j - weight[i]]),即保证物品在此轮遍历j中只放了一次。
5.举例推导dp数组
代码:
public static void main(String[] args) {
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
int bagWight = 4;
testWeightBagProblem(weight, value, bagWight);
}
public static void testWeightBagProblem(int[] weight, int[] value, int bagWeight){
int wLen = weight.length;
//定义dp数组:dp[j]表示背包容量为j时,能获得的最大价值
int[] dp = new int[bagWeight + 1];
//遍历顺序:先遍历物品,再遍历背包容量
for (int i = 0; i < wLen; i++){
for (int j = bagWeight; j >= weight[i]; j--){
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
}
//打印dp数组
for (int j = 0; j <= bagWeight; j++){
System.out.print(dp[j] + " ");
}
}
416. 分割等和子集
题目链接:416. 分割等和子集
一维的还不能完全掌握,先掌握二维的解法。
思路: 1.确定dp数组以及下标的含义
dp数组的定义 dp[i][j]从前i个数字中任意放入容量为j 总和的最大值。
2.确定递推公式
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - nums[i]] + nums[i])
3.dp数组如何初始化
和背包问题类似。
4.确定遍历顺序
二维可以从右到左。
5.举例推导dp数组
class Solution {
public boolean canPartition(int[] nums) {
int sum = 0;
for (int i : nums) {
sum += i;
}
if (sum % 2 == 1) {
return false;
}
sum /= 2;
// dp数组的定义 dp[i][j]从前i个数字中任意放入容量为j 总和的最大值
int[][] dp = new int[nums.length][sum + 1];
// 初始化 j为0时都为0
// i为0时
for (int j = nums[0]; j < sum; j++) {
dp[0][j] = nums[0];
}
for (int i = 1; i < nums.length; i++) {
for (int j = 1; j <= sum; j++) {
if (j >= nums[i]) {
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - nums[i]] + nums[i]);
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[nums.length - 1][sum] == sum;
}
}