背包问题:
在n个物品中挑选若干物品装入背包,最多能装多满?假设背包的大小为m,每个物品的大小为A[i]
注意事项:
你不可以将物品进行切割。
样例:
如果背包的大小为11,可以选择[2, 3, 5]装入背包,最多可以装满10的空间。
如果背包的大小为12,可以选择[2, 3, 7]装入背包,最多可以装满12的空间。
函数需要返回最多能装满的空间大小。
挑战:
O(n x m) time and O(m) memory.
O(n x m) memory is also acceptable if you do not know how to optimize memory.
算法分析:
本题为经典的背包问题,对于给定大小为n的物品数组A[]以及背包的容量m,建立一个二维数组DP[i][j],其表示对于前i个物品背包容量为j时背包中所能存放的最大的物品数量;
对于当前的一个物品i,可以选择放入背包,也可以选择不放入背包:
①放入背包,则必须要满足当前的物品在背包中放的下,因此DP[i][j] = DP[i-1][j-A[i]] + A[i];
②若不放入背包,则DP[i][j] = DP[i-1][j];
③综合①和②,DP[i][j] = max{DP[i-1][j-A[i]] + A[i],DP[i-1][j] };
对于本题的挑战,因为需要O(m) 的空间开销,所以可以建立两个数组DP[m] 与 DP_copy[m],其中DP_copy[]用来记录上一层的动态规划结果,DP[]用来改变当前层的动态规划结果,其效果与二维数组一样,但是空间开销较小;
算法正确性:
因为对于每一层DP[i]均表示为取到当前物品i时能取到的最大值,因此对于n时DP[n]也为最大值,可以用数学归纳法证明;
代码:
public class Solution { /** * @param m: An integer m denotes the size of a backpack * @param A: Given n items with size A[i] * @return: The maximum size */ public int backPack(int m, int[] A) { // write your code here int DP[] = new int[m+1]; int DP_copy[] = new int[m+1]; for(int i=0;i<A.length;i++){ for(int j=A[i];j<=m;j++){ DP[j] = Math.max(DP_copy[j],DP_copy[j-A[i]]+A[i]); } for(int k=A[i];k<=m;k++){ DP_copy[k] = DP[k]; } } return DP[m]; } }
背包问题II:
给出n个物品的体积A[i]和其价值V[i],将他们装入一个大小为m的背包,最多能装入的总价值有多大?
注意事项:
A[i], V[i], n, m均为整数。你不能将物品进行切分。你所挑选的物品总体积需要小于等于给定的m。
样例:
对于物品体积[2, 3, 5, 7]和对应的价值[1, 5, 2, 4], 假设背包大小为10的话,最大能够装入的价值为9。
挑战:
O(n x m) memory is acceptable, can you do it in O(m) memory?
算法分析:
本题在背包问题的基础上给每个物品增加了价值这一属性,其实本质是一致的,我们在DP数组中存放的不再是质量而是物品的价值value,保证DP[i] 是取前i个物品的所有情况中价值最大的那个情况,当然前提是背包中能够放下对应价值的物品。
代码:
public class Solution { /* * @param m: An integer m denotes the size of a backpack * @param A: Given n items with size A[i] * @param V: Given n items with value V[i] * @return: The maximum value */ public int backPackII(int m, int[] A, int[] V) { // write your code here int[] DP = new int[m+1]; int[] DP_copy = new int[m+1]; for(int j =0;j<A.length;j++){ for(int i=A[j];i<=m;i++){ DP[i] = Math.max(DP_copy[i],DP_copy[i-A[j]]+V[j]); } for(int i=A[j];i<=m;i++){ DP_copy[i] = DP[i]; } } return DP[m]; } }
背包问题VI:
给出一个都是正整数的数组 nums
,其中没有重复的数。从中找出所有的和为 target
的组合个数。
注意事项:
一个数可以在组合中出现多次。
数的顺序不同则会被认为是不同的组合。
样例:
[1, 2, 4]
, target =
4
可能的所有组合有:
[1, 1, 1, 1]
[1, 1, 2]
[1, 2, 1]
[2, 1, 1]
[2, 2]
[4]
返回 6
算法分析:
本题一开始看到还以为是用递归的方式遍历出所有的情况加以解决,但是毫无疑问这种方式解决问题绝对是复杂而又不可取的;
静下心来好好考虑了一下其实这道题是考了可重复取物品的背包问题:
①当背包容量为m时,若物品i的质量nums[i] < m, 则m的组合数数量就可以加上容量为m-nums[i]的组合数量;
②根据①中的描述可以建立一个一位数组DP[m]用来记录容量为m时该背包中物品质量恰好等于m的物品装载组合数;
③因此动态规划的表达式为DP[m] = sum{DP[m-nums[i]]},其中nums[i]<m;
代码:
public class Solution { /* * @param nums: an integer array and all positive numbers, no duplicates * @param target: An integer * @return: An integer */ public int backPackVI(int[] nums, int target) { // write your code here int[] DP = new int[target+1]; DP[0] = 1; for(int i=1;i<=target;i++){ for(int j=0;j<nums.length;j++){ if(nums[j]<=i){ DP[i] += DP[i-nums[j]]; } } } return DP[target]; } }
以上即为常见的三种背包问题的解决方法,它们均有一个共性就是记录对于当前容量i的最优解,以局部最优最终得出整体的最优解,这也是动态规划这一类问题的共性吧;然后有些问题看起来貌似与背包问题毫无联系,如之前的Partition Equal Subset Sum,但实质就是背包问题,而这类问题最容易想到的就是它的输入有物品(数组)同时也有总容量(Capacity);