容量为C
的包,一共有N
个物品(重量W[i]
,价值V[i]
),如何拿物品使得价值最大?
# 子问题:将前i件物品放入容量为j的背包中
# 转移方程:
# 如果不放第i件物品,那么问题就转化为“前i-1件物品放入容量为j的背包中”
# 如果放第i件物品,那么问题就转化为“前i-1件物品放入容量为j-w[i]的背包中”
# 时间复杂度为O(N*C)
# 空间复杂度为O(N*C)
def max_value(N, W, V, C):
if N == 0 or C == 0:
return 0
dp = np.zeros((N, C+1)) # 将前i件物品装进限重为j的背包可以获得的最大价值
for j in range(C+1):
if W[0] <= j:
dp[0][j] = V[0]
for i in range(1, N):
for j in range(C+1):
if W[i] > j:
dp[i][j] = dp[i-1][j]
else:
dp[i][j] = max(dp[i-1][j-W[i]] + V[i], dp[i-1][j])
return dp[-1][-1]
# 优化空间复杂度
# 由于转移方程中只用到了dp[i-1],那么可以进行空间复杂度的优化
def max_value(N, W, V, C):
if N == 0 or C == 0:
return 0
dp = np.zeros((C+1,))
for i in range(N):
for j in reversed(range(W[i], C+1)):
# 如果是顺序的话,dp[j-w[i]]的值就会被覆盖了,不是i-1时候的值了
dp[j] = max(dp[j], dp[j-W[i]] + V[i])
return dp[-1]
背包容量为C
,有N
种物品无限个(重量W[i]
,价值V[i]
),如何拿物品使得价值最大?
def max_value(N, W, V, C):
if N == 0 or C == 0:
return 0
dp = np.zeros((C+1,))
for i in range(N):
for j in reversed(range(W[i], C+1)):
# 应该为正序:装入第i种物品之后仍然能装第i种物品
dp[j] = max(dp[j], dp[j-W[i]] + V[i])
return dp[-1]
# 还有两种办法:转换为01背包问题
# 第一种 max(dp[i-1][j-k*W[i]] + k*V[i], dp[i-1][j])
# 第二种 max(dp[i-1][j-2^k*W[i]] + 2^k*V[i], dp[i-1][j])
容量为C
,N
种物品(n[i]
件, w[i]
重量,v[i]
价值),如何拿物品使得价值最大?
# 转换为01背包问题
# 第一种 max(dp[i-1][j-k*W[i]] + k*V[i], dp[i-1][j])
# 第二种 max(dp[i-1][j-2^k*W[i]] + 2^k*V[i], dp[i-1][j])
def max_value(N, n, w, v, C):
if N == 0 or C == 0:
return 0
# 拆分物体
w_new = []
v_new = []
for i in range(N):
k = 1
while k < n[i]:
w_new.append(k * w[i])
v_new.append(k * v[i])
k = k << 1
k = k >> 1
w_new.append(k * w[i])
v_new.append(k * v[i])
N_new = len(w_new)
dp = np.zeros((C+1,))
for i in range(N_new):
for j in reversed(range(w_new[i], C+1)):
dp[j] = max(dp[j], dp[j-w_new[i]] + v_new[i])
return dp[-1]
有的物品可以取1次,有的物品可以取无限次,有的物品可以取有限次(转换为01背包,多重背包和完全背包来解决)
背包承重A
,容量B
,N
种物品(重量a[i]
,体积b[i]
,价值v[i]
),如何拿物品使得价值最大?
# 状态也加一维即可
# dp[j][k]: 前i个物品放入承重j容量k的背包里的最大价值
def max_value(N:int,A:int, B:int, a:list[int], b:list[int], v:list[int]):
if N == 0 or A == 0 or B == 0:
return 0
dp = np.zeros((A+1, B+1))
for i in range(N):
for j in range(1, A+1):
for k in reversed(range(1, B+1)):
if j >= a[i] and k >= b[i]:
dp[j, k] = max(dp[j-a[i], k-b[i]] + v[i], dp[j][k])
return dp[-1][-1]
另外一种描述:背包承重A
,最多只能拾取M
件物品,N
种物品(重量a[i]
,价值v[i]
),那么就相当于每件物品的“件代价”为`。
这些物品被划分为N
组,每组中的物品(w[i][j]
, v[i][j]
)互相冲突,最多选一件。
# 转化为01背包问题
# 一组里面选增益最大的那个
def max_value(N:int,w, v, C):
if N == 0 or C == 0:
return 0
dp = np.zeros((C+1, ))
for i in range(N):
for j in reversed(range(C+1)):
for k in range(len(w[0])): # 对于每组物品的个数
if w[i][k] > j:
continue
dp[j] = max(dp[j], dp[j-w[i][k]] + v[i][k])
return dp[-1]
背包恰好装满时,初始化不同,只有dp[0]
初始化为0,其余初始化为-inf
即可。(因为最初什么都不装的时候,只有背包容量为0才满足“恰好装满”的条件)
# 转移方程dp的含义就更改为:第0....i件物品装入背包刚好装满j容量的方案数
def max_value(N, W, V, C):
if N == 0 or C == 0:
return 0
dp = np.zeros((C+1,))
for i in range(N):
for j in reversed(range(W[i], C+1)):
# 如果是顺序的话,dp[j-w[i]]的值就会被覆盖了,不是i-1时候的值了
dp[j] = sum(dp[j], dp[j-W[i]])
return dp[-1]
对于恰好装满的背包方案数,dp
的初始化就不应该是-inf
了,应该除了dp[0]
都初始化为0
对于01背包问题,需要记录每一个物品到底选了还是没选,所以需要多开辟O(N)
的空间保存这一选择。
416. 分割等和子集 - 力扣(LeetCode):恰好装满的01背包问题
322. 零钱兑换 - 力扣(LeetCode): 恰好装满的完全背包问题
494. 目标和 - 力扣(LeetCode):恰好装满的01背包问题
474. 一和零 - 力扣(LeetCode):二维01背包问题