第313题 0,1背包最大价值问题

106 阅读4分钟

问题描述

一个旅行者外出旅行时需要将 n 件物品装入背包,背包的总容量为 m。每个物品都有一个重量和一个价值。你需要根据这些物品的重量和价值,决定如何选择物品放入背包,使得在不超过总容量的情况下,背包中物品的总价值最大。

给定两个整数数组 weights 和 values,其中 weights[i] 表示第 i 个物品的重量,values[i] 表示第 i 个物品的价值。你需要输出在满足背包总容量为 m 的情况下,背包中物品的最大总价值。

题目分析

本题是经典的0/1 背包问题,在算法竞赛、面试和日常工程中非常常见。其特点是选择性容量限制,在有限资源下追求最大收益。问题描述如下:

  1. 旅行者有 nn 件物品,每件物品有重量和价值;
  2. 背包的容量为 mm,不能超过此重量;
  3. 需要选择物品,使得装入背包的物品总价值最大。

我们需要编写一个算法,接受输入 nnweights[]weights[]values[]values[]mm,输出能获得的最大总价值。


解题思路

这个问题的本质是一个动态规划问题。通过使用状态定义状态转移方程初始化条件,我们可以将问题转化为求解一个最优值。

1. 动态规划简介

动态规划是一种通过分解问题并保存子问题解的方法,用于解决最优化问题。对于背包问题,每件物品只有不选两种情况,而背包有一个容量限制。因此,我们需要根据前面选择的物品状态,推导当前的最优解。


2. 状态定义

我们用一个一维数组 dp[j] 表示:
当背包容量为 jj 时,能获得的最大总价值。

这里的容量 jj 是从 00mm,表示背包在不同容量下的最优状态。


3. 状态转移方程

对于每个物品 ii,我们有两种选择:

  • 不选择物品 ii 背包的最大价值与前面的状态相同,即 dp[j] 不变;
  • 选择物品 ii 在选择 ii 的情况下,背包需要减去物品 ii 的重量,同时增加物品 ii 的价值,即 dp[j - weights[i]] + values[i]

因此,状态转移方程为:

dp[j]=max(dp[j],dp[jweights[i]]+values[i])dp[j] = \max(dp[j], dp[j - weights[i]] + values[i])

这个公式表示,对于当前背包容量 jj,我们选择是否加入第 ii 件物品,以保证总价值最大。


4. 遍历顺序

  • 外层循环: 遍历物品 ii
  • 内层循环: 遍历背包容量 jj,从 mmweights[i]weights[i] 逆序更新。
    为什么逆序?因为我们希望当前状态 dp[j] 不被覆盖,仍能使用上一轮的状态 dp[j - weights[i]]

5. 初始化

  • 对于容量为 00 的背包,无论放什么物品,总价值都是 00,所以 dp[0] = 0
  • 其他容量 jj 的初始值也可以设为 00,表示当前没有物品装入背包。

6. 返回结果

最终,dp[m] 表示在背包容量为 mm 时,能装入的最大总价值。


代码实现

以下是完整代码实现:

def solution(n: int, weights: list, values: list, m: int) -> int:
    # 初始化 DP 数组
    dp = [0] * (m + 1)

    # 遍历每个物品
    for i in range(n):
        # 逆序遍历容量
        for j in range(m, weights[i] - 1, -1):
            dp[j] = max(dp[j], dp[j - weights[i]] + values[i])

    # 返回最大总价值
    return dp[m]

代码详解

1. 初始化 DP 数组

我们使用一个长度为 m+1m+1 的数组 dp 表示不同容量下的最大总价值:

dp = [0] * (m + 1)

初始时,dp[j] = 0,因为还没有装入物品。


2. 遍历每个物品

对于每件物品 ii,我们需要检查它是否能够放入当前容量 jj 的背包中:

for i in range(n):  # 遍历每个物品

3. 逆序遍历背包容量

为了防止当前轮次更新覆盖之前的状态,我们从最大容量 mm 开始遍历:

for j in range(m, weights[i] - 1, -1):  # 逆序遍历
    dp[j] = max(dp[j], dp[j - weights[i]] + values[i])
  • dp[j] 表示不选第 ii 件物品;
  • dp[j - weights[i]] + values[i] 表示选择第 ii 件物品。

两者取最大值即可。


复杂度分析

1. 时间复杂度

  • 外层循环 nn 次,表示遍历每个物品;
  • 内层循环 mm 次,表示遍历背包容量;
  • 总时间复杂度为 O(n×m)O(n \times m)

2. 空间复杂度

  • 只需要一个长度为 m+1m+1 的数组 dp,空间复杂度为 O(m)O(m)

优化方向

  1. 空间优化: 如果需要记录选择的物品,可以用二维数组 dp[i][j],表示前 ii 个物品的状态。

  2. 变体问题:

    • 完全背包:每件物品可以选择多次;
    • 多重背包:每件物品有数量限制。

总结

动态规划通过将问题分解为子问题并利用其结果,显著减少了计算量。对于本题,核心在于明确状态定义推导转移方程。通过一维数组优化,空间复杂度降为 O(m)O(m),非常高效!