0、1背包最大价值问题 | 豆包MarsCode AI刷题

138 阅读5分钟

题目描述

image.png

这道题是典型的0-1 背包问题,目标是在不超过背包容量的情况下,选择物品使得总价值最大化。下面是解决这个问题的思路:

image.png

解决思路

  1. 问题建模

    • 给定 n 个物品,每个物品有重量 weights[i] 和价值 values[i]
    • 背包容量为 m,要求在不超过容量的情况下选择物品,使得总价值最大。
  2. 动态规划方法

    • 使用一个一维动态规划数组 f,其中 f[j] 表示容量不超过 j 时可以获得的最大价值。
    • 初始化 f 数组的长度为 m+1,每个位置初始值为 0,表示在背包没有物品时的初始价值。
  3. 状态转移

    • 遍历每个物品 i,对于每个物品的重量 weights[i],在容量 jmweights[i] 之间逆序更新 f 数组。
    • 使用逆序遍历是为了保证每个物品只被选择一次,即避免重复使用物品。
    • 对于每个容量 j,更新公式为: f[j]=max⁡(f[j],f[j−weights[i]]+values[i])f[j] = \max(f[j], f[j - weights[i]] + values[i])f[j]=max(f[j],f[j−weights[i]]+values[i]) 这个公式表示,如果选择当前物品 i,那么在原有的最大价值 f[j - weights[i]] 的基础上加上当前物品的价值 values[i],从而更新最大价值 f[j]
  4. 最终结果

    • 当所有物品遍历完后,f[m] 就表示在背包容量不超过 m 的情况下可以获得的最大价值。
def solution(n: int, weights: list, values: list, m: int) -> int:
    # 初始化动态规划数组,长度为 m+1,所有元素都设置为 0
    f = [0] * (m + 1)
    
    # 遍历每个物品
    for i in range(n):
        # 从 m 到 weights[i] 的逆序遍历,确保每个物品只被计算一次
        for j in range(m, weights[i] - 1, -1):
            # 更新 f 数组,存储在不超过重量 j 的情况下可以获得的最大价值
            f[j] = max(f[j], f[j - weights[i]] + values[i])
    
    # 返回在不超过重量 m 的情况下可以获得的最大价值
    return f[m]

if __name__ == '__main__':
    print(solution(3, [2, 1, 3], [4, 2, 3], 3) == 6)
    print(solution(4, [1, 2, 3, 2], [10, 20, 30, 40], 5) == 70)
    print(solution(2, [1, 4], [5, 10], 4) == 10)

时间复杂度分析

  1. 初始化数组

    f = [0] * (m + 1)
    

    初始化一个大小为 m+1 的数组,时间复杂度是 O(m)

  2. 遍历每个物品

    for i in range(n):
        for j in range(m, weights[i] - 1, -1):
    

    对于每个物品 i(共有 n 个物品),我们需要遍历从 mweights[i] 的容量值,即逆序遍历。最坏情况下,内层循环的遍历次数为 m 次,因此每个物品的时间复杂度为 O(m)

  3. 状态转移公式

    f[j] = max(f[j], f[j - weights[i]] + values[i])
    

    该操作的时间复杂度为 O(1),每次更新 f[j] 数组的元素。

总时间复杂度

  • 外层循环:O(n)
  • 内层循环:O(m)

因此,总的时间复杂度为 O(n * m)

空间复杂度分析

  • 我们只使用了一个大小为 m+1 的数组 f,来记录不同容量下的最大价值,因此空间复杂度是 O(m)

样例分析

样例 1

输入:

solution(3, [2, 1, 3], [4, 2, 3], 3)
  • 物品数:3
  • 重量:[2, 1, 3]
  • 价值:[4, 2, 3]
  • 背包容量:3

动态规划过程

  1. 初始化 f = [0, 0, 0, 0],表示背包容量从 0 到 3 时的最大价值。

  2. 遍历第一个物品(重量 2,价值 4):

    • f[3] = max(f[3], f[3-2] + 4) = max(0, 0 + 4) = 4,更新 f = [0, 0, 0, 4]
    • f[2] = max(f[2], f[2-2] + 4) = max(0, 0 + 4) = 4,更新 f = [0, 0, 4, 4]
  3. 遍历第二个物品(重量 1,价值 2):

    • f[3] = max(f[3], f[3-1] + 2) = max(4, 0 + 2) = 4f[3] 不变。
    • f[2] = max(f[2], f[2-1] + 2) = max(4, 0 + 2) = 4f[2] 不变。
    • f[1] = max(f[1], f[1-1] + 2) = max(0, 0 + 2) = 2,更新 f = [0, 2, 4, 4]
  4. 遍历第三个物品(重量 3,价值 3):

    • f[3] = max(f[3], f[3-3] + 3) = max(4, 0 + 3) = 4f[3] 不变。

最终结果为 f[3] = 6,表示在背包容量为 3 的情况下,能够获得的最大价值为 6。

输出

True  # 表示计算正确

样例 2

输入:

solution(4, [1, 2, 3, 2], [10, 20, 30, 40], 5)
  • 物品数:4
  • 重量:[1, 2, 3, 2]
  • 价值:[10, 20, 30, 40]
  • 背包容量:5

动态规划计算过程:

  • 通过类似的过程,最终 f[5] = 70,表示在背包容量为 5 的情况下,能够获得的最大价值为 70。

输出

True  # 表示计算正确

样例 3

输入:

solution(2, [1, 4], [5, 10], 4)
  • 物品数:2
  • 重量:[1, 4]
  • 价值:[5, 10]
  • 背包容量:4

最终结果为 f[4] = 10,表示在背包容量为 4 的情况下,能够获得的最大价值为 10。

输出

True  # 表示计算正确

总结

  1. 时间复杂度O(n * m),其中 n 为物品数量,m 为背包容量。
  2. 空间复杂度O(m),由于使用了一个大小为 m+1 的数组。
  3. 通过动态规划解决了 0-1 背包问题,保证了每个物品只被计算一次,最终返回在给定背包容量下能够获取的最大价值。

此问题常见于背包类问题的变种,适合用动态规划方法来解决,通过优化每个状态的计算,能够在合适的时间内获得最优解。