问题背景
旅行者需要将 n 件物品装入背包中,背包的总容量为 m。每件物品有一个重量和一个对应的价值,目标是选择合适的物品放入背包,使得总价值最大化,同时不超过背包的容量。
输入:
n:物品数量weights:长度为n的整数数组,表示每个物品的重量values:长度为n的整数数组,表示每个物品的价值m:背包的总容量
输出:
- 背包在容量不超过
m的情况下,能够装入物品的最大总价值。
问题分析
这是一个典型的01背包问题(0/1 Knapsack Problem)。在该问题中,对于每个物品,我们有两个选择:
- 不将该物品放入背包;
- 将该物品放入背包。
对于每个物品 i,如果我们选择放入背包,那么我们就需要将背包的容量减少 weights[i],并将总价值增加 values[i]。
通过动态规划,我们可以通过构造一个二维数组 dp[i][j] 来表示前 i 件物品,在背包容量为 j 的情况下可以获得的最大价值。
动态规划公式
-
初始化:
dp[0][j] = 0,表示没有物品时,所有容量的最大价值为0。dp[i][0] = 0,表示背包容量为0时,最大价值也是0。
-
状态转移:
-
如果当前背包容量
j小于物品i的重量weights[i-1],则不能将该物品放入背包: dp[i][j]=dp[i−1][j]dp[i][j] = dp[i-1][j] -
如果当前背包容量
j大于等于物品i的重量weights[i-1],则我们有两种选择:- 不放物品
i,最大价值为dp[i-1][j]; - 放入物品
i,最大价值为dp[i-1][j-weights[i-1]] + values[i-1]。
dp[i][j]=max(dp[i−1][j],dp[i−1][j−weights[i−1]]+values[i−1])dp[i][j] = \max(dp[i-1][j], dp[i-1][j-weights[i-1]] + values[i-1])
- 不放物品
-
最终的答案就是 dp[n][m],即前 n 件物品,背包容量为 m 时能够获得的最大价值。
代码实现
def solution(n: int, weights: list, values: list, m: int) -> int:
# 初始化dp数组,dp[i][j]表示前i个物品,背包容量为j时的最大价值
dp = [[0] * (m + 1) for _ in range(n + 1)]
# 填充dp数组
for i in range(1, n + 1):
for j in range(1, m + 1):
if j >= weights[i - 1]: # 如果背包容量足够放当前物品
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weights[i - 1]] + values[i - 1])
else: # 否则不放当前物品
dp[i][j] = dp[i - 1][j]
# 返回最大价值
return dp[n][m]
# 测试用例
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
代码解析
-
初始化
dp数组:- 创建一个二维数组
dp,其中dp[i][j]表示前i个物品在背包容量为j时的最大价值。 dp[i][j]初始值为0,表示当没有物品或背包容量为0时,背包的价值为0。
- 创建一个二维数组
-
动态规划填充
dp数组:- 外层循环
i遍历物品,内层循环j遍历背包的容量。 - 对于每个物品和容量,我们根据是否能够放入当前物品来更新
dp[i][j]。
- 外层循环
-
返回最大价值:
- 最终结果存储在
dp[n][m]中,表示前n件物品,背包容量为m时的最大价值。
- 最终结果存储在
测试用例
-
用例1:
print(solution(n = 3, weights = [2, 1, 3], values = [4, 2, 3], m = 3)) # 期望输出: 6解释:
- 物品 1 的重量为 2,价值为 4;
- 物品 2 的重量为 1,价值为 2;
- 物品 3 的重量为 3,价值为 3。
- 背包容量为 3,选择物品 1 和物品 2 组合,最大价值为 4 + 2 = 6。
-
用例2:
print(solution(n = 4, weights = [1, 2, 3, 2], values = [10, 20, 30, 40], m = 5)) # 期望输出: 70解释:
- 选择物品 1、物品 3 和物品 4,能够获得最大价值 10 + 30 + 40 = 70。
-
用例3:
print(solution(n = 2, weights = [1, 4], values = [5, 10], m = 4)) # 期望输出: 10解释:
- 背包容量为 4,选择物品 2 的话可以获得最大价值 10。
复杂度分析
-
时间复杂度:
- 外层循环遍历物品数
n,内层循环遍历背包容量m。因此,总时间复杂度为O(n * m)。
- 外层循环遍历物品数
-
空间复杂度:
- 我们使用了一个
dp数组,其空间复杂度为O(n * m)。
- 我们使用了一个
总结
通过动态规划求解01背包问题是经典的算法技巧。此问题展示了如何通过状态转移方程在约束条件下最大化背包的价值。