问题一:有N个物品,重量为A[0]...A[N-1],有一个容量为M(M是一个正整数)。
问:最多能带走多重的物品。
例:A = [2,3,5,7]
M = 10
输出:10(2,3,5)
问题分析:
要求N个物品能否拼出重量W(W = 0,1...M),需要知道前N-1个物品能否拼出重量W(W = 0,1...M)
考虑最后一个物品(A[N-1])放不放入进入背包
情况1:如果前N-1个物品能拼出重量W,则前N个物品必然也能拼出重量W
情况2:如果前N-1个物品能拼出重量W-A[N-1],则前N个物品就能拼出重量W,加上物品A[N-1]即可
子问题:
设:f[i][w]表示物品前i个能拼出重量w(True/False)
f[i][w] = f[i-1][w] or f[i-1][w-A[i-1]]
不放入A[i-1] or 放入A[i-1]
初始条件:
0个物品可以拼出重量0
f[0][0] = True
0个物品不能拼出大于0的任何重量
f[0][1...M] = False
边界情况:
f[i][w-A[i-1]] w>=A[i-1]时使用
计算顺序:
初始化
f[0][0..M]
前1个物品能拼出:f[1][0]...f[1][M]
.
.
前N个物品能拼出:f[N][0]...f[N][M]
时间复杂度:O(MN),空间复杂度O(MN),优化后可以达到O(M)
代码及注释如下:
def backpack(A,M):
N = len(A)
if N == 0:
return 0
f = [[False for i in range(M+1)] for j in range(N+1)]
#初始化,f[0][0] = True ;f[0][1...M] = False
f[0][0] = True
for i in range(1,N+1):
for j in range(0,M+1):
#f[i][w]表示物品前i个能拼出重量w(True/False)
#f[i][w] = f[i-1][w] or f[i-1][w-A[i-1]](w>=A[i-1])
f[i][j] = f[i-1][j]
if j >= A[i-1] :
f[i][j] = f[i-1][j] or f[i-1][j-A[i-1]]
#返回前N个物品能拼出最大的重量,肯定不会超过M,因为最大就是M
for j in range(M+1)[::-1]:
if f[N][j]:
return j
A = [2,3,7,5]
M = 10
print(backpack(A,M))
#结果:10
问题二:假设每个物品只有一个(每个物品只能用一次),问一共有多少种方式正好凑成重量Target?
例:
A = [1,2,3,3,7],Target = 7
输出:2(1,3,3,7)
问题分析:
如果知道这N个物品有多少种方式拼出0...Target,也就得到了答案
确定状态:需要N个物品有多少种方式拼出重量W(W = 0...Target)
最后一步:考虑第N个物品A[N-1](最后一个物品)是否进入背包
case1: 不进入,用前N-1个物品拼出W
case2: 进入,前N-1个物品能拼出W-A[N-1],加上最后一个A[N-1],正好拼出W
现在要求的是方式数,
case1的方式数+case2的方式数 = 用前N个物品拼出W的方式数
转移方程:设f[i][w]表示用前i个物品能拼出w的方式数
f[i][w] =