LintCode刷题笔记-- BackpackII
标记:
动态规划
问题描述:
Given n items with size Ai, an integer m denotes the size of a backpack. How full you can fill this backpack?
Example
If we have 4
items with size [2, 3, 5, 7]
, the backpack size is 11, we can select [2, 3, 5]
, so that the max size we can fill this backpack is 10
. If the backpack size is 12
. we can select [2, 3, 7]
so that we can fulfill the backpack.
You function should return the max size we can fill in the given backpack.
解题方法:
快要死了,自己想了将近3个小时,居然卡在了如何保持物品放入的唯一性上了,中间用了标记位,哈希集的方法来解决都宣告失败。这一道题还是还是典型的动态规划的背包问题。今天已经可以很自然的划分出子问题来了,并且可以想到利用矩阵的数据结构来存储子状态,并且利用子状态的解来进一步解决问题。但是关于子问题之间的关系和递推方程的领悟还需要进一步的练习,毕竟动态规划是最难的,不是可能轻易领悟的。再一次向九章算法的答案致敬(真是太牛逼了!如果不看这个答案想死了我也想不出这样的解法来。)
好的言归正传,下面是解题思路:
1.划分子问题:
这一题的主问题是什么?非常典型的背包问题的变种。一列物品放入背包之中怎么可以使放入物品达到最大重量,这一问题与先前解决的问题最大的不同在于所有放入的物品是唯一的,就是只能放一次。当然子问题划分与之前并没有什么很大的变化,还是从物品个数和最大重量的两个向量上来划分。列出物品数量*最大重量的矩阵dp[0....i][0....m],期中dp[i][j]所代表的意义在于在最大重量为j时,同时又i种物品可以选,是否存在恰好的有效解,即正好可以有几样物品的重量之和等于当前所给的最大重量。所以dp这一矩阵的数据类型设置为boolean。
2.初始状态定义:
对于所有情况开始都是未知,所以dp整个矩阵可以定义为false,即开始都不存在恰好的解决方案
dp[0][0] = true 作为一个恒成立的情况,即0个物品0个重量时是存在的。
3.子问题之间的关系(递推方程):
这一部分也是所有动态规划最难,最精妙的地方,一个人的逻辑如何,算法能力如何,基本都体现在这一步。
如果在重量j上存在j大于当前物品集合A[i]的重量且在前一物品集合中j-A[i]的位置上也存在恰好解:
例如:[3,4,8,5]四个物品最大的重量时10
0.在0个物品中,重量为0时存在恰好解
1.在只有3的一个物品的情况下重量为0,3时存在恰好解。
2.在存在3,4两个物品的情况下重量为3,4,7时存在恰好解
3.在存在3,4,8三个物品时重量为3,4,7时存在恰好解
4.在存在3,4,8,5三个物品重量为3,4,7,9时存在恰好解
在2中判断重量为7时是否存在恰好解会先判断1中7-4=3的位置是否存在恰好解,若存在此处恰好加上物品4等于7.
若不满足要求则与前一物品情况相同
4.终止状态:
当遍历完全部情况以后,再对存在所有物品的情况中,在不同重量上进行遍历,返回存在恰好解的最大重量,则为题目的解。
5.参考代码:
1 public int backPack(int m, int[] A) { 2 // write your code here 3 4 boolean[][] dp = new boolean[A.length+1][m+1]; 5 6 for(int i = 0; i<=A.length; i++){ 7 for(int j = 0; j<=m; j++){ 8 dp[i][j] = false; 9 } 10 } 11 dp[0][0] = true; 12 13 for(int i = 0; i<A.length; i++){ 14 for(int j=0; j<=m; j++){ 15 dp[i+1][j] = dp[i][j]; 16 if(j>=A[i]&&dp[i][j-A[i]]){ 17 dp[i+1][j] = true; 18 } 19 } 20 } 21 22 for(int i=m; i>=0; i--){ 23 if(dp[A.length][i]){ 24 return i; 25 } 26 } 27 return 0; 28 }