背包问题
二维01 背包
有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
对于背包问题,有一种写法, 是使用二维数组,即dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
只看这个二维数组的定义,大家一定会有点懵,看下面这个图:
2.确定递归表达式
dp[i][j]的含义:从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少
dp[i][j]可以从两个方向推出,选择第i个物品和不选择第i个物品
1.选择第i个物品:dp[i][j]=max(dp[i-1][j],dp[i-1][j-wight[i]]+value[i]),选择价值大的
2.不选第i个物品:dp[i][j]=dp[i-1][j],因为没有选所以和选前i-1个物品价值一样
3.dp数组如何初始化
关于初始化,一定要和dp数组的定义吻合,否则到递推公式的时候就会越来越乱。
首先从dp[i][j]的定义出发,如果背包容量j为0的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为0。如图:
当i为0时,即当选择物品0 时,当容量小于物品0的体积时,dp[0][j]=0,否则为value[0];
4.确定遍历顺序
在如下图中,可以看出,有两个遍历的维度:物品与背包重量
本题都可以,先遍历物品在遍历背包较好理解
import java.util.Scanner;public class Main {public static int M;// M 代表研究材料的种类,public static int N;// N,代表小明的行李空间。public static void main(String[] args) {Scanner sc=new Scanner(System.in);M=sc.nextInt();N=sc.nextInt();int[]wight=new int[M];//材料的重量int[]value=new int[M];//材料的价值for (int i = 0; i <M ; i++) {wight[i]=sc.nextInt();}for (int i = 0; i <M ; i++) {value[i]=sc.nextInt();}int[][]dp=new int[M][N+1];for (int i = 0; i <M ; i++) {dp[i][0]=0;}for (int i = wight[0]; i <=N ; i++) {dp[0][i]=value[0];}for (int i = 1; i <M ; i++) {for (int j = 1; j <=N ; j++) {if(j<wight[i]){//不放dp[i][j]=dp[i-1][j];}else {dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-wight[i]]+value[i]);}}}System.out.println(dp[M-1][N]);}
}
一维dp数组(滚动数组)
1.确定dp数组的定义
在一维dp数组中,dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]。
2.一维dp数组的递推公式
dp[j]为 容量为j的背包所背的最大价值,那么如何推导dp[j]呢?
dp[j]可以通过dp[j - weight[i]]推导出来,dp[j - weight[i]]表示容量为j - weight[i]的背包所背的最大价值。
所以递归公式为:
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j],那么dp[0]就应该是0,因为背包容量为0所背的物品的最大价值就是0。
2.一维dp数组遍历顺序
倒序遍历是为了保证物品i只被放入一次!
代码如下:
for(int i = 0; i < weight.size(); i++) { // 遍历物品for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);}
}
import java.util.Scanner;public class Main {public static int M;// M 代表研究材料的种类,public static int N;// N,代表小明的行李空间。public static void main(String[] args) {Scanner sc = new Scanner(System.in);M = sc.nextInt();N = sc.nextInt();int[] wight = new int[M];//材料的重量int[] value = new int[M];//材料的价值for (int i = 0; i < M; i++) {wight[i] = sc.nextInt();}for (int i = 0; i < M; i++) {value[i] = sc.nextInt();}int[] dp = new int[N + 1];dp[0] = 0;for (int i = 0; i < M; i++) {for (int j = N; j >= wight[i]; j--) {dp[j] = Math.max(dp[j], dp[j - wight[i]] + value[i]);}}System.out.println(dp[N]);}
}
分割等和子集
416. 分割等和子集 - 力扣(LeetCode)
- 背包的体积为sum / 2
- 背包要放入的商品(集合里的元素)重量为 元素的数值,价值也为元素的数值
- 背包如果正好装满,说明找到了总和为 sum / 2 的子集。
- 背包中每一个元素是不可重复放入。
dp[j]表示 背包总容量(所能装的总重量)是j,放进物品后,背的最大重量为dp[j]。
dp[j]=max(dp[j],dp[j-nums[i]]+nums[i])//nums[i]对应背包问题既是重量也是价值
当dp[j]==j时说明可以找到子集合等于j所以本题判断dp[targrtSum]==targetSum即可(targetSum==sum/2)
class Solution {public boolean canPartition(int[] nums) {int sum=0;int target=0;for (int i = 0; i < nums.length ; i++) {sum+=nums[i];}if(sum%2==1){return false;}target=sum/2;int[] dp = new int[10001];dp[0] = 0;for (int i = 0; i < nums.length; i++) {for (int j = target; j >=nums[i]; j--) {dp[j]=Math.max(dp[j],dp[j-nums[i]]+nums[i]);}}return dp[target]==target;}
}