1 类型
2 重点:背包问题中,数组的大小和总承重有关
3 动态规划的四步骤:
4 描述: 你有一个背包,背包有最大承重,商店里有若干物品,都是免费拿,每个物品有重量和价值
目标:不撑爆背包的前提下
逐个放物品,看是否还能放入:两个关键点 :还有几个物品; 还剩多少承重
5 例题
1)单一物品----->最值型
http://www.lintcode.com/en/problem/backpack/
题意:给定N个物品,重量分别为正整数A0,A1, …, AN-1, 一个背包最大承重是正整数M,最多能带走多重的物品
例子:
输入:4个物品,重量为2, 3, 5, 7. 背包最大承重是11
输出:10 (三个物品:2, 3, 5)
解题思想:
一、确定状态
需要知道N个物品是否能拼出重量W (W =0, 1, …, M)
最后一步:最后一个物品(重量AN-1)是否进入背包0 1 2 … M-1 M
子问题:要求前N个物品能不能拼出重量0, 1, …, M,需要知道前N-1个物品能不能拼出重量0, 1, …, M
状态:设f[i][w] = 能否用前i个物品拼出重量w (TRUE / FALSE)
二、转移方程
设f[i][w] = 能否用前i个物品拼出重量w (TRUE / FALSE)
f[i][w] = f[i-1][w] OR f[i-1][w-Ai-1]
三:初始条件和边界情况
初始条件:
边界情况:
f[i-1][w-Ai-1]只能在w≥Ai-1时使用
四:计算顺序
代码:
public static int test3(int[] nums,int C){ if(nums.length==0||nums==null) return 0; int length=nums.length; boolean[][] f=new boolean [length+1][C+1]; int maxWeight=0; f[0][0]=true; for(int i=1;i<=C;i++){ f[0][i]=false; } for(int i=1;i<=length;i++){ f[i][0]=true; for(int j=1;j<=C;j++){ if(j>=nums[i-1]){ f[i][j]=f[i-1][j]||f[i-1][j-nums[i-1]]; }else{ f[i][j]=f[i-1][j]; } } } for(int i=C;i>=0;i--){ if(f[length][i]){ maxWeight=i; break; } } return maxWeight; }
时间复杂度(计算步数):O(MN),空间复杂度(数组大小):优化后可以达到O(M)
小结
• 要求不超过Target时能拼出的最大重量
• 记录前i个物品能拼出哪些重量
• 前i个物品能拼出的重量:
– 前i-1个物品能拼出的重量
– 前i-1个物品能拼出的重量+第i个物品重量Ai-1
2)单一物品----->计数型
http://www.lintcode.com/problem/backpack-v/
题意:给定N个正整数:A0,A1, …, AN-1,一个正整数Target。求有多少种组合加起来是Target,每个Ai只能用一次
例子:
输入:A=[1, 2, 3, 3, 7], Target=7
输出:2 (7=7, 1+3+3=7)
解题思路
一、确定状态
需要知道N个物品有多少种方式拼出重量W (W =0, 1, …, Target)
最后一步:第N个物品(重量AN-1)是否进入背包
情况一:用前N-1个物品拼出W
情况二:用前N-1个物品能拼出W- AN-1 ,再加上最后的物品AN-1 ,拼出W
情况一的个数+情况二的个数=用前N个物品拼出W的方式
子问题:要求前N个物品有多少种方式拼出重量0, 1, …, Target,需要知道前N-1个物品有多少种方式拼出拼出重量0, 1, …, Target
二、转移方程
设f[i][w] = 用前i个物品有多少种方式拼出重量w
f[i][w] = f[i-1][w] + f[i-1][w-Ai-1]
三、初始条件和边界情况
初始条件:
– f[0][0] = 1: 0个物品可以有一种方式拼出重量0
– f[0][1..M] = 0: 0个物品不能拼出大于0的重量
边界情况:
– f[i-1][w-Ai-1]只能在w≥Ai-1时使用
四、计算顺序
代码:
//对空间进行优化:从后往前遍历,新生成的f[j]可以覆盖前面用到的f[j]; public static int test3(int[] A,int m){ int length=A.length; if(length<=0) return 0; int[] f=new int[m+1]; f[0]=1; for(int i=1;i<=length;i++){ for(int j=m;j>=0;j--){ // f[i][j]=f[i-1][j]+f[i-1][i-A[i-1]]; if(j-A[i-1]>=0) //如果为假,就直接等于前面的f[j]; //如果为真:f[i][j]=f[i-1][j]+f[i-1][i-A[i-1]]----> f[j]+=f[j-A[i-1]]; //这句代码有两个作用,一是判断新生成的f[j],而是用新生成的f[j]来代替老的f[j],实现数组的动态更新 f[j]+=f[j-A[i-1]]; } } return f[m]; }
时间复杂度(计算步数):O(N*Target),空间复杂度(数组大小):优化后可以达到O(Target)
3)无限多物品----->计数型
http://www.lintcode.com/problem/backpack-vi/
题意:给定N个正整数:A0,A1, …, AN-1, 一个正整数Target,求有多少种组合加起来是Target,每个Ai可以用多次
例子:
输入:A = [1, 2, 4], Target = 4
输出:6(1+1+1+1=4, 2+2=4, 1+1+2=4, 1+2+1=4, 2+1+1=4, 4=4)
解题思路
一:确定状态
最后一步:最后一个物品的重量是多少
关键点1:任何一个正确的组合中,所有物品总重量是Target
关键点2:如果最后一个物品重量是K,则前面的物品重量是Target-K
子问题:原问题要求有多少种组合能拼成Target,变成有多少种组合可以拼成Target – A0,...Target – AN.
二:转移方程
设f[i] = 有多少种组合能拼出重量i
f[i] = f[i-A0] + f[i-A1] +…+ f[i-AN-1]
三:初始条件和边界情况
初始条件:f[0] = 1,有1种组合能拼出重量0 (什么都不选)
边界情况:如果i 四:计算顺序 代码: 时间复杂度(计算步数): O(N*Target),空间复杂度:O(Target) 4)单个物品----->价值最大型 http://www.lintcode.com/problem/backpack-ii/ 题意:给定N个物品,重量分别为正整数A0,A1, …, AN-1,价值分别为正整数V0,V1, …, VN-1, 一个背包最大承重是正整数M,最多能带走多大价值的物品 例子: 输入:4个物品,重量为2, 3, 5, 7,价值为1, 5, 2, 4. 背包最大承重是11 输出:9 (物品一+物品三,重量3+7=10,价值5+4=9) 解题思路 一:确定状态 和前一题类似,需要知道N个物品,是否能拼出重量W (W =0, 1, …, M),对于每个重量W,最大总价值是多少 最后一步:最后一个物品(重量AN-1, 价值VN-1)是否进入背包 子问题:要求前N个物品能不能拼出重量0, 1, …, M,以及拼出重量W能获得的最大价值,需要知道前N-1个物品能不能拼出重量0, 1, …, M,以及拼出重量W能获得的最大价值 二:转移方程 设f[i][w] = 用前i个物品拼出重量w时最大总价值(-1表示不能拼出w) f[i][w] = max{f[i-1][w], f[i-1][w-Ai-1] + Vi-1 | w≥Ai-1 且f[i-1][w-Ai-1] ≠-1} 三:初始条件和边界情况 初始条件: 边界情况:f[i-1][w-Ai-1]只能在w≥Ai-1,并且f[i-1][w-Ai-1] ≠-1时使用 四:计算顺序 代码: 时间复杂度(计算步数):O(MN),空间复杂度(数组大小):优化后可以达到O(M) 5)无限多物品----->价值最大型 http://www.lintcode.com/problem/backpack-iii/ 题意:给定N种物品,重量分别为正整数A0,A1, …, AN-1,价值分别为正整数V0,V1, …, VN-1,每种物品都有无穷多个,一个背包最大承重是正整数M 最多能带走多大价值的物品 例子: 输入:4个物品,重量为2, 3, 5, 7,价值为1, 5, 2, 4. 背包最大承重是10 输出:15 (3个物品一,重量3*3=9,价值5*3=15) 解题思路 无穷多物品的解题思路可以参考上面的计数型的解题思路,只不过这时候加上了价值,求价值的最大值。 代码: max=max>counts[i]?max:counts[i]; } return max; } 时间复杂度(计算步数):O(MN),空间复杂度O(M) 6 总结 可行性背包 – 题面:要求不超过Target时能拼出的最大重量,记录f[i][w]=前i个物品能不能拼出重量w 计数型背包 – 题面:要求有多少种方式拼出重量Target:记录f[i][w]=前i个物品有多少种方式拼出重量w 最值型背包 – 题面:要求能拼出的最大价值:记录f[i][w]=前i个/种物品拼出重量w能得到的最大价值 • 关键点 最后一步 • 最后一个背包内的物品是哪个,最后一个物品有没有进背包,数组大小和最大承重Target有关 • 空间优化
public static int test(int[] nums, int target ){
int length=nums.length;
if(nums.length==0)
return 0;
int counts[]=new int[target+1];
counts[0]=1;
for(int i=1;i<=target;i++){
counts[i]=0;
for(int j=0;j
public static int test(int[] weight,int[] values,int m){
int length=weight.length;
if(length==0)
return 0;
int nums[][]=new int[length+1][m+1];
nums[0][0]=0;
for(int i=1;i<=m;i++){
nums[0][i]=-1;
}
//可以按照combinatioSum4 这个类里面的把数组压缩,压缩成一维数组,要注意的是如果压缩就一定要倒着求;
for(int i=1;i<=length;i++){
nums[i][0]=0;
for(int j=1;j<=m;j++){
nums[i][j]=nums[i-1][j];
if(j>=weight[i-1]&&nums[i-1][j-weight[i-1]]!=-1){
nums[i][j]=Math.max(nums[i-1][j],nums[i-1][j-weight[i-1]]+values[i-1]);
}
}
}
int maxValue=nums[length][1];
for(int i=2;i<=m;i++){
if(nums[length][i]>maxValue)
maxValue=nums[length][i];
}
return maxValue;
}
public static int test(int[] weight,int[] values,int m){
int length=weight.length;
if(length==0)
return 0;
int[] counts=new int[m+1];
counts[0]=0;
int max=0;
for(int i=1;i<=m;i++){
counts[i]=-1;
for(int j=0;j