今日收获:背包问题(二维),背包问题(一维),分割等和子集
1. 背包问题理论基础
2. 背包问题(二维)
题目链接:46. 携带研究材料(第六期模拟笔试)
思路:dp[i,j] 表示从[0,i]的物品中选择,放入容量为 j 的背包中,可以获取的最大价值总和;二维数组需要初始化第一行和第一列,即从物品0选择和容量为0的情况;遍历顺序是一层层从左到右遍历(因为下一行的值是通过上一行同一列的值和左上角的某个值推出来的),递推公式是不放入物品i(即当前位置的上一行)和放入物品i(留出物品i重量的上一行的某个位置+物品i的价值)两者的最大值。
方法:
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
int M=sc.nextInt();
int N=sc.nextInt();
int[] cost=new int[M];
int[] value=new int[M];
for (int i=0;i<M;i++){
cost[i]=sc.nextInt();
}
for (int i=0;i<M;i++){
value[i]=sc.nextInt();
}
int[][] dp=new int[M][N+1]; // dp数组
// 初始化
for (int i=0;i<M;i++){
dp[i][0]=0;
}
for (int j=0;j<N+1;j++){
if (j>=cost[0]){
dp[0][j]=value[0];
}
}
// 先遍历物品,再遍历背包
for (int i=1;i<M;i++){
for (int j=1;j<N+1;j++){
if (j<cost[i]){ // 还不能放下当前物品
dp[i][j]=dp[i-1][j];
}else { // 可以放下当前物品
// 递推公式,放还是不放当前物品
dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-cost[i]]+value[i]);
}
}
}
System.out.println(dp[M-1][N]);
}
}
2. 背包问题(一维)
思路:
(1)dp数组的定义:在上面的思路中,当前位置的值都是由上一行推导出来的,所以可以把二维dp数组压缩到一维。dp[j]表示背包容量为 j 时的最大物品价值。
(2)初始化:在递推时,dp数组取两种可能的最大值,所以初始化为0就可以。
(3)遍历顺序:背包容量从大到小遍历,这样可以防止同一个物品被多次放入背包。而且要先遍历物品再遍历背包容量,反过来先遍历背包容量再遍历物品的话,会导致背包中只能放入一个物品。
(4)递推公式:dp=Math.max(dp[j],dp[j-cost[i]]+value[i]);
方法:
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
int M=sc.nextInt();
int N=sc.nextInt();
int[] cost=new int[M];
int[] value=new int[M];
for (int i=0;i<M;i++){
cost[i]=sc.nextInt();
}
for (int i=0;i<M;i++){
value[i]=sc.nextInt();
}
int[] dp=new int[N+1]; // 一维dp数组
// 先遍历物品,再倒序遍历背包容量
for (int i=0;i<M;i++){
for (int j=N;j>0;j--){
if (j>=cost[i]){ // 如果容量能放得下当前物品
dp[j]=Math.max(dp[j],dp[j-cost[i]]+value[i]);
}
}
}
System.out.println(dp[N]);
}
}
3. 分割等和子集
题目链接:416. 分割等和子集 - 力扣(LeetCode)
思路:
(1)将此问题转换为01背包问题,背包的容量是目标值,数的价值和花费都是值本身,然后判断判断容量为target时的值是否为target。
(2)一维dp数组的定义:数组元素表示当背包容量为i时可以取到的最大值。
(3)初始化:因为集合中的元素都为正整数,所以可以初始化数组全为0,保证递推时元素的值不会被初始值覆盖。
(4)遍历顺序和递推公式都按照一维dp数组的要求。
方法:
class Solution {
public boolean canPartition(int[] nums) {
int len=nums.length;
int sum=0;
// 计算数组的总和,如果是奇数就剪枝
for (int i=0;i<len;i++){
sum+=nums[i];
}
if (sum%2==1){
return false;
}
int target=sum/2;
int[] dp=new int[target+1]; // dp数组表示容量为i时可以取得的最大值
// 先遍历数,再倒序遍历背包
for (int i=0;i<len;i++){
for (int j=target;j>0;j--){
if (j>=nums[i]){
dp[j]=Math.max(dp[j],dp[j-nums[i]]+nums[i]);
}
}
if (dp[target]==target){ // 背包的价值一满就返回,不用完全遍历数组
return true;
}
}
return dp[target]==target;
}
}