0-1背包问题
有一个容量为w的背包,有x件物品,物品的重量为weight[i],价值为value[i],每件物品只有一个,问怎样装物品才能使背包里物品的价值最大。
1、二维数组写法
1、确定dp数组含义:dp[i][j]表示背包容量为j时,装0-i之间的物品所能获取到的最大价值
2、确定递推公式:
当该物品的重量比背包容量大时,不能装该物品。dp[i][j] = dp[i-1][j]; 背包最大价值等于背包容量为j时,装到i-1号物品时的最大价值。
当该物品可以装进去时,dp[i][j] = Math.max(dp[i-1][j],dp[i-1][ j-weight[i] ]+value[i]); 此时背包的最大价值为:背包容量为j,不装该物品时背包的价值;dp[i-1][ j-weight[i] ](背包装到i-1号物品,背包容量为j-weight[i] 时背包的最大价值。因为可以装下该物品,就取装到i-1号物品时,装物品i正好填满当前背包的容量j,就是j-weight[i])有点难理解
3、初始化
当背包容量为0时,背包的价值为0,也就是dp[i][0]等于0;
只 装0号物品时,背包的最大价值为0号物品的价值。dp[0][j] = value[0];(前提是背包容量大于0号物品的重量)
4、遍历顺序:
从第一个物品开始遍历,依次遍历完所有的物品
public static void main(String[] args) {
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
//背包最大容量
int bagsize = 4;
int [][] dp = method(weight,value,bagsize);
for(int [] arr : dp){
for(int i : arr)
System.out.print(i+" ");
System.out.println();
}
}
public static int[][] method(int []weight,int []value,int bagsize){
//dp[i][j] 在0-i号物品中任选,装入容量为j的背包,最大价值
int [][]dp = new int[weight.length][bagsize+1];
// 初始化,当背包容量为0时,物品价值应该为0
for(int i = 0;i<dp.length;i++){
dp[i][0] = 0;
}
//初始化,先把0号物品放入各个容量情况下的背包。
for(int i = 0;i<dp[0].length;i++){
if(weight[0] <= i) dp[0][i] = value[0];
else dp[0][i] = 0;
}
for(int i = 1;i<dp.length;i++){
for(int j = 1;j<dp[0].length;j++){
if(weight[i] > j) dp[i][j] = dp[i-1][j];
else dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i]);
}
}
return dp;
}
上面的二维数组可以优化成一维数组,但是背包容量要从大往小开始遍历。
public static int[] method2(int[] weight,int[] value,int bagsize){
//背包容量为j时,背包的最大价值
int dp[] = new int[bagsize+1];
//背包容量为0时,背包最大价值为0
dp[0] = 0;
for(int i = 0;i<weight.length;i++){
for(int j = bagsize;j>=weight[i];j--){
dp[j] = Math.max(dp[j],dp[j-weight[i]]+value[i]);
}
}
return dp;
}
0-1背包问题的应用
分割等和的子集
把数组中的数分割成两个等和的子集,说明每个子集的和等于数组中所有数的总和sum的一半,所以转化为背包问题为:
1、背包的体积为sum / 2
2、背包要放入的商品(集合里的元素)重量为 元素的数值,价值也为元素的数值
3、背包如果正好装满,说明找到了总和为 sum / 2 的子集。
4、背包中每一个元素是不可重复放入。
题目中只是分割成两个等和的子集,若从其中选出一些数的和等于总和的一半。那么另外一些数的总和一定等于数组总和的一半。
public boolean canPartition(int[] nums) {
int sum = 0;
for(int x:nums){
sum += x;
}
if(sum%2 != 0) return false;
int target = sum/2;
int dp[] = new int[target+1];
for(int i = 0;i<nums.length;i++){
for(int j = dp.length-1;j>=nums[i];j--){
dp[j] = Math.max(dp[j],dp[j-nums[i]] + nums[i]);
}
}
return dp[dp.length-1] == target;
}