目录
知识点1:0-1背包理论基础1
- 首先需要明白dp数组的含义。dp [i] [j]:物体在0-1之间选取,最大背包重量为j时,这时最大的价值。
- dp数组的迭代公式:dp[i][j]: 基于两种情况而来:
(1):dp[i][j]=dp[i-1][j],即虽然可供挑选的物品多了一个(物体i),但是i没有放进来
(2):dp[i][j]=dp[i-1][j-weight[i]]+value[i],把物体i放进来了,但是需要腾出weight[i]的重量,即要找dp[i-1][j-weight[i]]的大小。
(3):比较两者大小,看看需不需要放进来:dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]]; - 进行初始化:在java中,如果数组没有赋值,默认为0.需要对dp[0][j]进行初值的赋予。
- 遍历的方式:可以先遍历物体再遍历重量(先右后下);可以先遍历重量再遍历物体(先下后右)
//0——1背包问题:先遍历物品,再遍历重量/先遍历重量,再遍历物品(二维数组实现)
public class beibao {
public static void main(String[] args) {
//都dp数组的定义和初始化(默认为0)
int[] weight=new int[]{3,2,1};
int[] value=new int[]{15,20,30};
int maxweight = 4;
int[][] dp=new int[weight.length][maxweight+1];
//1)dp数组初始化
for(int j=weight[0]; j<=4; j++){
dp[0][j]=value[0];
}
//2)遍历方式和迭代公式:先遍历物品,再遍历重量/先遍历重量,再遍历物体一样的。
for(int i=1;i<weight.length;i++){
for(int j=0;j<=4;j++){
if(j<weight[i]) {
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]);
}
}
}
//3)日志的打印验证
System.out.println(Arrays.deepToString(dp));
System.out.println("result="+dp[weight.length-1][4]);
}
}
//0——1背包问题:先遍历物体,再遍历重量,顺序不能乱,按行来的(一维数组实现)
public class beibao1 {
public static void main(String[] args) {
//都dp数组的定义和初始化(默认为0)
int[] weight = new int[]{1, 3, 4};
int[] value = new int[]{15, 20, 30};
int maxweight = 4;
//1)dp数组的定义
int[] dp = new int[maxweight + 1];
//2)初始化一维数组:
for (int i = 0; i <= maxweight; i++) {
if (i < weight[0]) {
dp[i] = 0;
} else {
dp[i] = value[0];
}
}
System.out.println("dp=" + Arrays.toString(dp));
//进行遍历循环:先遍历物体,再遍历重量(这个顺序不能乱;可以从头开始遍历,前提是第一行需要初始化
int num = weight.length - 1; //物品的数量
for (int i = 1; i < weight.length; i++) { //先遍历物品,因为有了初始初始化,可以从i=1开始
for (int j = weight[i]; j <= maxweight; j++) { //遍历重量,从符合物品的重量开始。
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
System.out.println("dp=" + Arrays.toString(dp));
}
}
}
知识点2:0-1背包理论基础2
我们经过迭代过程可以发现,当物体进行变更的时候,只是在前一行的基础上修改了一点点内容,这样,我们可以用一个一维数组表示。
2.1 第一类背包问题:容量为k背包,存放物品,使得价值最大
- 首先需要明白dp数组的含义:dp [j]:最大背包重量为j时,这时最大的价值
- dp数组的迭代公式:
(1): 比较两者大小,看看需不需要放进来:dp[j]=Math.max(dp[j],dp[j-weight[i]]+value[i]) - 进行初始化:使用默认初始化即可
- 遍历方式:
(1):先物体再重量,因为是倒序,如果先重量后物体,那么背包每一个时刻只有一个值。
(2):在重量遍历的时候,从后先前,这样能够使得第一行满足条件,不用初始化第一行
(3):在遍历重量的时候,重量至少要大于等于当前物体的重量,不然就不变,都不需要比较大小。
//第一种普通背包问题,最优美的代码
public class beibao2 {
public static void main(String[] args) {
//定义dp数组,并初始化(默认为0,自动初始化)
int[] weight = new int[]{1, 3, 4};
int[] value = new int[]{15, 20, 30};
int maxweight = 4;
//1)数组的定义,默认初始化零,通过倒序遍历可以省略初始化
int[] dp = new int[maxweight + 1];
for (int i = 0; i < weight.length; i++) {
for (int j = maxweight; j >= weight[i]; j--) {
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
System.out.println("dp=" + Arrays.toString(dp));
}
}
}
2.2 第二类背包问题:容量为k背包,存放物品,装满有多少种可能性
//背包的第二类问题
public class beibao3 {
public static void main(String[] args) {
int[] nums=new int[]{1,1,1,1,1};
int weight=4;
//初始化数组:
int[] dp=new int[weight+1];
dp[0]=1;
//进行遍历迭代
for(int i:nums){
for(int j=weight;j>=i;j--){
dp[j]+=dp[j-i]; //用到了i或者不用i
}
}
System.out.println("result="+dp[weight]);
}
}
2.3 总结:0-1背包问题
- 第一类01背包问题:
1)问题描述:若干个物品,每个物品有对应的重量和价值,当背包大小固定为K时,如何装存物品,使得背包中物品的价值最大?
2)求解方法:
(1):用一维dp数组:使用默认初值0;双层遍历,先正序遍历物品,再逆序遍历重量
(2):用二维dp数组:第一行初值赋予,双层遍历:物品和重量不分先后遍历,正序和逆序都可以。 - 第二类01背包问题:
1)问题描述:容量为k背包,存放物品,装满有多少种可能性?
2)求解方法:
(1):用一维dp数组:dp[0]=1;双层遍历,先正序遍历物品,再逆序遍历重量,迭代方法为累加,求的是组合数;先逆序遍历重量,再正序遍历物品,迭代方法为累加,求的是排列数。
题目一:416. 分割等和子集(0-1背包第一类问题)
- 题目说明
- 求解步骤
1)把该问题转变为一个01背包问题。(每个物体只能取一次)。
2)相当于计算判断:当背包容量为sum/2的时候,其最大价值是不是sum/2 - 代码展示
class Solution {
public boolean canPartition(int[] nums) {
//核心思路:分割为两个,那么每个的大小就是总和的一半;
//可以用01背包问题,在容量为总和一半的时候,装入更大的value值等于总和一半。
//dp数组进行初始化
int sum=0;
for(int i:nums) sum+=i;
if(sum%2==1) return false;
int[] dp=new int[sum/2+1];
//01背包问题(先物品,后重量,物品顺序无所谓,重量倒序可以免除初始化)
for(int i=0;i<nums.length;i++){
for(int j=sum/2; j>=nums[i]; j--){
dp[j]=Math.max(dp[j],dp[j-nums[i]]+nums[i]);
}
}
if(dp[sum/2]==sum/2) return true;
return false;
}
}
题目二:494. 目标和(0-1背包第二类问题)
-
题目说明
-
求解步骤
1)把问题转化为第二种类型的背包问题:对于若干个物品,有多少种存储方式,能够刚好装满容量为k的背包、
2)P=abs((target+sum)/2) :问题的转换
class Solution {
public int findTargetSumWays(int[] nums, int target) {
//01背包问题另一种:放入一定的数,求使得背包满了的存放种数
//首先获得需要搜寻的背包大小
int num=0;
for(int i: nums) num+=i;
if((target+num)%2==1) return 0;
int p=(target+num)/2;
if(p<0) p=-p; //target可以为负数,由于值为均为正整数,我们需要进行变号
int[] dp=new int[p+1]; //背包需要的容量
//初始化dp数组值
dp[0]=1;
//进行遍历寻值[从后向前,累加,这个思想非常重要]:数i放进去和不放进去,两种可能性,需要累加
for(int i:nums){
for(int j=p;j>=i;j--){
dp[j]+=dp[j-i];
}
}
return dp[p];
}
}
题目三:474.一和零(0-1背包第一类问题,二维)
- 题目描述
- 解题步骤
1)首先这是一个0-1背包问题,只是背包变成了两个而已,但是思路和0-1背包第一种一模一样。
2)对于字符串,要需要设计一个函数,求解器0和1的个数。 - 代码详解
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
//1)相当于有两个书包的0-1背包第一类问题
//需要一个子方法,把字符串中的0和1的个数拿出来
//相当于大小有两个维度,而价值都为1
//1)dp数组的定义
int [][] dp=new int[m+1][n+1];
//2)dp数组的遍历(先遍历物品,再一次遍历包)
for(int i=0;i<strs.length;i++){
//0的个数
int size0=method(strs[i]);
int size1=strs[i].length()-method(strs[i]);
for(int j=m;j>=size0;j--){
for(int z=n;z>=size1;z--){
dp[j][z]=Math.max(dp[j][z],dp[j-size0][z-size1]+1);
}
}
}
return dp[m][n];
}
//方法:统计一个字符串的0的个数
public static int method(String string){
int result=0;
for(int i=0;i<string.length();i++){
if(string.charAt(i)=='0') result++;
}
return result;
}
}
总结:0-1背包题目
- 问能否能装满背包(或者最多装多少)?
1)迭代公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
2)参考题目:题目一:416. 分割等和子集
3)参考题目题目三:474.一和零 - 问装满背包有几种方法?
1)迭代公式:dp[j] += dp[j - nums[i]]
2)参考题目:题目二:494. 目标和 - 遍历的顺序:一维需要先遍历物品,再遍历重量,然后物品是正序,重量是逆序。