一维dp数组(滚动数组)
leetcode中无纯0-1背包问题,可从卡码网上查看题目46.0-1背包问题
一维数组来源于二维数组,其本质是对一维数组进行压缩了,压缩后需要注意在进行背包容量循环的时候采用后序遍历,而不能采用前序遍历。
在使用二维数组的时候,递推公式为
dp[i][j] =max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i])
表示在0-i个物品中任意挑选放进容量为j的背包里所能够获得的最大价值
与其把dp[i - 1]这一层拷贝到dp[i]上,不如只用一个一维数组了,只用dp[j](一维数组,也可以理解是一个滚动数组)。
这就是滚动数组的由来,需要满足的条件是上一层可以重复利用,直接拷贝到当前层。
还是动规五步曲
1.确定dp数组的定义
dp[j]表示容量为j的背包所能够背的最大价值是dp[j],继续沿用j来表示背包容量,和前面的二维数组能够有所对应,也便于思维上思考
2.一维dp数组的递推公式
二维的递推公式:dp[i][j] =max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i])
去掉i这个维度得:dp[j] = max(dp[j],dp[j-weight[i]]+value[i])
dp[j]表示容量为j的背包所能背的最大价值
dp[j-weight[i]]表示容量为j-weight[i]所能够背的最大价值
dp[j-weight[i]]+value[i]表示容量为j-weight[i]的背包里装了第i个物品后的最大价值
此时dp[j]有两个选择,一个是取自己dp[j] 相当于 二维dp数组中的dp[j],即不放物品i,一个是取dp[j - weight[i]] + value[i]
,即放物品i,指定是取最大的,毕竟是求最大价值
3.初始化
关于初始化,一定要和dp数组的定义吻合,否则到递推公式的时候就会越来越乱。
dp[j]表示容量为j的背包能够背的最大价值是dp[j]
当j=0的时候,即背包容量为0,那就是dp[0] = 0
其他的均可初始化为0
4.一维dp数组的遍历顺序
for i in range(n):
for j in range(bagweight,weight[i]-1,-1):
dp[j] = max(dp[j],dp[j-weight[i]]+value[i])
倒序遍历是为了保证物品i只被放入一次!。但如果一旦正序遍历了,那么物品0就会被重复加入多次!
举一个例子:物品0的重量weight[0] = 1,价值value[0] = 15
如果正序遍历
dp[1] = dp[1 - weight[0]] + value[0] = 15
dp[2] = dp[2 - weight[0]] + value[0] = 30
此时dp[2]就已经是30了,意味着物品0,被放入了两次,所以不能正序遍历。
为什么倒序遍历,就可以保证物品只放入一次呢?
倒序就是先算dp[2]
dp[2] = dp[2 - weight[0]] + value[0] = 15 (dp数组已经都初始化为0)
dp[1] = dp[1 - weight[0]] + value[0] = 15
所以从后往前循环,每次取得状态不会和之前取得状态重合,这样每种物品就只取一次了。
切记这里一维数组中遍历的时候只能先遍历物品,再遍历背包容量,并且背包的容量只能后序遍历
5.举例dp数组
代码
n,bagweight = map(int, input().split())
weight = list(map(int, input().split()))
value = list(map(int, input().split()))
#dp[j]表示容量为j的背包所背的最大价值
dp = [0]*(bagweight+1)
dp[0] = 0
for i in range(n):
for j in range(bagweight,weight[i]-1,-1):
dp[j] = max(dp[j],dp[j-weight[i]]+value[i])
print(dp[bagweight])