问题
一个旅行者外出旅行时需要将 n
件物品装入背包,背包的总容量为 m
。每个物品都有一个重量和一个价值。你需要根据这些物品的重量和价值,决定如何选择物品放入背包,使得在不超过总容量的情况下,背包中物品的总价值最大。
给定两个整数数组 weights和values,其中 weights[i] 表示第 i个物品的重量,values[i]
表示第 i
个物品的价值。你需要输出在满足背包总容量为 m
的情况下,背包中物品的最大总价值。
分析
在上题中,由于每个物体只有两种可能的状态(取与不取),对应二进制中的0和 1,这类问题便被称0-1 背包问题,也是动态规划的经典问题之一。
题中已知条件有第i个物品的重量w[i],价值v[i],以及背包的总容量W。 设状态 dp[i, j]为在只能放前i个物品的情况下,容量为j的背包所能达到的最大总价值。 假设当前已经处理好了前i-1个物品的所有状态,那么对于第i个物品,只有放与不放两种状态。当其不放入背包时,背包已经消耗的容积不变,背包的剩余容量不变,背包中物品的总价值也不变,故这种情况的价值为dp[i - 1, j];当其放入背包时,背包的剩余容量会减小w[i],背包中物品的总价值会增大v[i],故这种情况下,可以得到的最大总价值为价值为dp[i-1, j-w[i]] + v[i],这里需要满足w[i]<j<W,这样可以保证放入第i件物品后不超过最大容积,且容积为j的背包可以放下第i件物品。我们仅需考虑放与不放这两种情况的最大值即可。
由此可以得出状态转移方程:dp[i, j] = max(dp[i - 1, j], dp[i - 1, j - w[i]] + v[i])
优化
考虑到影响dp[i, ...] 的只有dp[i - 1, ...],因此,该问题可以优化为使用滚动数组。此时dp[j] 为当前背包容积为j时的最大价值,这样我们就将dp数组从一个二维数组转成了一个一维数组。此时dp[j]有两个选择,一个是不放物品i,最大价值为dp[j],相当于二维dp数组中的dp[i-1][j]。一个是放物品i,最大价值为dp[j -w[i]] +v[i], 相当于dp[i - 1, j - w[i]] + v[i]。由此,我们得到递推公式:dp[j] = max(dp[j], dp[j - w[i]] + v[i])
对于当前处理的物品和当前状态dp[i, j],在j-w[i]时,dp[i, j]是会被 dp[i - 1, j - w[i]]所影响的。这就相当于物品i 可以多次被放入背包,与题意不符。
为了避免这种情况发生,我们可以改变枚举的顺序,从W枚举到w[i],就不会出现上述的错误,因为dp[i, j]总是在dp[i - 1, j - w[i]]前被更新。
代码
def solution(n: int, weights: list, values: list, m: int) -> int:
dp = [0] * (m + 1)
for i in range(0, n):
for j in range(m, weights[i] - 1, -1):
dp[j] = max(dp[j], dp[j - weights[i]] + values[i])
return max(dp)
if __name__ == '__main__':
print(solution(n = 3, weights = [2, 1, 3], values = [4, 2, 3], m = 3) == 6)
print(solution(n = 4, weights = [1, 2, 3, 2], values = [10, 20, 30, 40], m = 5) == 70)
print(solution(n = 2, weights = [1, 4], values = [5, 10], m = 4) == 10)