题目描述
给你一个可装载重量为W的背包和N个物品,每个物品有重量和价值两个属性。其中第i个物品的重量为wt[i]
,价值为val[i]
,现在让你用这个背包装物品,最多能装的价值是多少?
举个简单的例子,输入如下:
N = 3, W = 4
wt = [2, 1, 3]
val = [4, 2, 3]
输出为6。
第一步
第一步需要明确两点,[状态]
和[选择]
状态,是指如何才能描述一个问题局面。只要给定几个可选物品和一个背包的容量限制,就形成了一个背包问题。因此状态有两个,就是背包的容量
和可选择的物品
。
选择也很简单,对于每件物品,你能选择什么?选择就是装进背包
或者不装进背包
。
明白了状态和选择,则动态规划问题基本上就解决了。
for 状态1 in 状态1的所有取值:
for 状态2 in 状态2的所有取值:
for ...
dp[状态1][状态2][...]=择优(选择1,选择2...)
第二步 明确dp数组含义
dp[i][w]
的定义如下:对于前i
个物品,当前的背包容量为w
,这种情况下可以装的最大价值是dp[i][w]
。
根据这个定义,我们想求的答案就是dp[N][W]
。
边界条件
base case
就是dp[0][...]=dp[...][0]=0
,因为没有物品或者背包没有空间时,能装的最大价值就是0。
第三步 思考状态转移的逻辑
简单来说,把物品i装进背包
和不把物品i装进背包
如何用代码体现出来。
dp[i][w]
表示:对于前i
个物品,当前背包的容量为w
时,这种情况下可以装下的最大价值是dp[i][w]
。
如果你没有把这第i
个物品装入背包,那么很显然,最大价值dp[i][w]
应该等于dp[i-1][w]。你不装嘛,那就继承之前的结果。
如果你把这第i
个物品装入了背包,那么dp[i][w]
应该等于dp[i-1][w-wt[i-1]] + val[i-1]
。
这里要注意的是,i
是从1
开始取的,因此对于val
和wt
,第i
个物品的重量或价值的取值应该是i-1
。
而dp[i-1][w-wt[i-1]]
也很好理解:你如果想装第i个物品,你怎么计算这时候的最大价值?换句话说,在装第i
个物品的前提下,背包能装的最大价值是多少?
显然,你应该寻求剩余重量w-wt[i-1]
限制下能装的最大价值,加上第i个物品的价值val[i-1]
,这就是装第i
个物品的前提下,背包可以装的最大价值。
最后代码
def knapsack(W, N, wt, val):
dp = [[0] * (W + 1) for _ in range(N + 1)]
for i in range(1, N + 1):
for w in range(1, W + 1):
if w - wt[i - 1] < 0:
dp[i][w] = dp[i - 1][w]
else:
dp[i][w] = max(dp[i - 1][w - wt[i - 1]] + val[i - 1], dp[i - 1][w])
return dp[N][W]
print(knapsack(4, 3, [2, 1, 3], [4, 2, 3]))
#output:
6
注意
每次看到边界条件时,我其实就会非常的不爽。因为这样最后导致了用wt[i-1],val[i-1]来表示第i个物品的价值和重量。我在想,能否不要这个边界条件呢。
我在尝试去编程的时候发现不可以。原因在于,动态规划其实最好有初始状态值,再一步一步进行迭代是最容易的。