1、典型背包问题(可参考文章)
题目:给定 n 件物品,物品的所占空间为 w[i],物品的价值为 c[i]。现挑选物品放入背包中,假定背包能承受的最大容量为 V,问应该如何选择装入背包中的物品,使得装入背包中物品的总价值最大?
分析:这是一个典型背包问题是求解 容量一定 的情况下 物品总价值最高的情况
如:音响 价值3k 容量4 电脑 价值2k 容量3 吉他 价值1.5k 容量1 背包容量为4 (w=[4,3,1] c=[3,2,1.5]) 可以列如下的动态网格:
0 1 2 3 # 商品个数n
0 0 0 0 0
1 0 1.5 1.5 1.5
2 0 1.5 1.5 1.5
3 0 1.5 2 2
4 0 1.5 3.5 3.5
# 容量情况v
状态定义:d[i][j] 代表背包容量为i时候 判断当存在第j个物品时(可能装也可能没装)背包的最大价值。
状态转移方程: d[i][j] = max(不装该物品时候的价值,该物品的价值+剩余空间的价值) = max(d[i][j-1],c[j-1]+d[i-w[j-1]][j-1])
初始状态: 为避免讨论 通常多设置一行 可以认为第0个物品的价值为0
多设置一列 可以认为背包没有空间的情况下
复杂度: 时间复杂度O(vn) 空间复杂度O(vn)
代码:
def findMaxFormBB(W, C, v):
n = len(W)
d = [[0 for i in range(n+1)] for j in range(v+1)]
maxval = 0
for i in range(1, v+1):
for j in range(1, n+1):
if i >= W[j-1]:
d[i][j] = max(d[i][j-1], C[j-1] + d[i - W[j-1]][j-1])
else:
d[i][j] = d[i][j-1]
maxval = max(maxval, d[i][j])
return maxval
2、变形背包问题(LC474. 一和零)
题目:给你一个二进制字符串数组 strs 和两个整数 m 和 n 。请你找出并返回 strs 的最大子集的大小,该子集中 最多 有 m 个 0 和 n 个 1 。
分析:
问题重述:
该问题是一个背包问题,当例子为strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3,可做如下解读:有一组物品(A B C D E)包含S1(0)、S2(1)两个小物品的数量分别为 A:1和1、B:3和1、C:2和4、D:0和1、E:1和0, 问如何取ABCD四个物品使得S1、S2分别小于5和3且物品数量是最多的。
这道题要满足 两个容量一定的情况下 物品数量 最多的情况 所以需要设计一个三维表格
状态定义: d[i][j][k] 代表 S1的数量为i、S2的数量为j时,添加到第k个物品时 物品的总量 (两个容量一点一点的试,物品一点一点的试 讨论的标准是选或者不选)
状态转移方程:d[i][j][k] = max(不选这个物品时候的数量,选这个物品时的数量) =
max(d[i][j][k-1],1 + d[i-第k物品包含S1的数量][j-第k物品包含S2的数量][k-1])
初始状态: 为了避免讨论 此处我们认为第0个字符串是空串 故 d[:][:][0] = 0
注意点:
当 S1的数量为0-m 时,都满足情况 故 第一个遍历的范围为 [0,m+1]
当 S2的数量为0-n 时,都满足情况 故 第一个遍历的范围为 [0,n+1]
复杂度: 时间复杂度O(mnl) 空间复杂度O(mnl)
PS: ① 学习动态规划解变形 (三维)01背包问题
② 学习计算每个字符串中0 1个数的方法
③ 利用 for range初始化 要注意 如果想以 ijk(分别为mnl的遍历下标)访问,则初始化时要按照kji来初始化。
举个例子 d = [[0]*2 for _ in range(3)] 得到的数组是 3*2的。最后一个元素是d[2][1].
代码:
def findMaxForm(strs, m, n):
l = len(strs)
d = [[[0 for _ in range(l+1)] for _ in range(n+1)] for _ in range(m+1)] # 初始化 如果想按照 i j k 访问 怎 按照 k j i初始化
zeros = [sum(1 for c in s if c == '0') for s in strs] # 学习计算每个字符串中0 1个数的方法
ones = [sum(1 for c in s if c == '1') for s in strs]
for i in range(m+1): # 注意i,j要从0开始 否则会跳过没有0或没有1的字符串
for j in range(n+1):
for k in range(1, l+1):
# 当 0的上限i大于 当前字符串的0的个数 且 1的上限大于 当前字符串1的个数 注意一下第k个字符串 对应存储其0 1个数的下标
if i >= zeros[k-1] and j >= ones[k-1]:
d[i][j][k] = max(d[i][j][k - 1], 1 + d[i - zeros[k-1]][j - ones[k-1]][k-1])
else:
d[i][j][k] = d[i][j][k - 1]
return d[-1][-1][-1]