题目:有n件物品和一个最多能背重量为w的背包。第i件物品的重量时weight[i],得到的价值是value[i]。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
解法一:dp二维数组
1.dp数组的含义
dp[i][j]表示将下标为(0,i)的物品任取,装入到最大容量为j的背包中,价值总和最大为多少。
2.dp数组的递推公式
物品i不放入容量为j的背包中可以用dp[i-1][j]表示。
物品i想要放入容量为j的背包中时,背包里至少要剩余容量weight[i]才可以,放入后背包中的价值总和为dp[i][j-weight[i]]+value[i]。
所以dp[i][j]=max( dp[i-1] [j] , dp[i-1] [ j-weight[i] ]+value[i] )
3.dp数组初始化
如果背包容量j为0,那么哪个物品都无法装入,所以dp[i][0]=0
由第二步得出的递推公式可知,i是由i-1推导出来的,那么i=0时一定要初始化。dp[0][j]代表着把下标为0的物品装入到容量为j的背包中的最大价值。当j<weight[0]时,dp[0][j]=0,当j>=weight[0]时,dp[0][j]=value[0]。
其他下标的初始值是什么都可以,因为后面都会被覆盖掉,经常会选择把它们都初始化为0。
4.遍历顺序
dp数组是一个二维数组,有两个遍历维度:物品,背包重量(容量)。
结论是:先遍历物品也可以,先遍历背包重量也可以。正序遍历倒序遍历也都可以。一般都选择先正序遍历物品,再正序遍历背包重量。
为什么都可以呢?观察递推公式,dp[i][j]是靠i-1那一行的数据推导出来的,先遍历物品后遍历背包重量的情况下计算i行时i-1行已经计算过了,先遍历背包重量后遍历物品的情况下dp[i-1] [j],dp[i-1] [ j-weight[i] ]都已经计算过了。
解法二:dp一维数组(滚动数组)
1.dp数组的含义
二维数组把i-1层的数据拷贝到i层,递推公式就可以写成
dp[i][j]=max( dp[i] [j] , dp[i] [ j-weight[i] ]+value[i] )
此时完全可以使用只保留j,使用一维数组表示该递推公式
dp[j]=max( dp[j] , dp [ j-weight[i] ]+value[i] )
dp[j]代表容量为j的背包,所背的物品价值最大可以是多少。
2.dp数组的递推公式
dp[j]=max( dp[j] , dp [ j-weight[i] ]+value[i] )
3.dp数组初始化
根据递归公式,dp数组在推导的时候一定是取价值最大的数。
如果题目给的价值都是正整数,那么非0下标都初始化为0就可以了。
如果题目给的价值有负数,则将非0下标初始化为负无穷。
这样才能让dp数组在递归公式的过程中取得最大价值,而不是被初始值覆盖。
4.遍历顺序
外层for循环遍历物品(正序),内层for循环遍历背包重量(倒序)。
内层倒序保证了每个物品只放了一次,但如果正序遍历,那么一个物品会被重复多次加入。
举例说明:物品0的重量weight[0]=1,价值value[0]=15。
如果正序遍历,dp[1]=dp[1-weight[0]]+value[0]=15,
dp[2]=dp[2-weight[0]]+value[0]=15+15=30。
物品0被加入了两次!
如果倒序遍历,dp[2]=dp[2-weight[0]]+value[0]=0+15=15
dp[1]=dp[1-weight[0]]+value[0]=0+15=15
问:为什么二维dp不用倒序,一维dp就要倒序呢?
答:二维dp中计算dp[i][j]使用的一直是i-1行的数据,没有受到i行数据的影响,而一维dp中计算i行的dp[j]初始化是i-1行数据,但在推导的过程中会逐渐覆盖掉i-1行的值,计算时会受到i行数据的干扰。
问:为什么不能先遍历背包重量,再遍历物品呢?
答:因为一维dp的写法,背包容量一定是要倒序遍历,如果遍历背包容量放在外层,那么每个dp[j]只能放入一个物品,即:背包里只放了一个物品。