背包问题 | 豆包MarsCode AI刷题

90 阅读4分钟

在日常生活中,背包问题(Knapsack Problem)是一个经典的优化问题。假设有一个旅行者外出旅行,他需要带一些物品,而每个物品都有一个重量和一个价值。旅行者的背包有一个固定的容量,他需要选择一些物品来装入背包,使得背包中物品的总价值最大,并且不超过背包的容量。这种问题不仅限于旅行,也广泛应用于物流、资源分配、财务管理等领域。

问题描述

给定两个数组 weightsvalues,分别表示每个物品的重量和价值,同时给定一个背包的总容量 m。你需要选择物品,使得在不超过背包总容量的情况下,背包中物品的总价值最大。

解题思路

这类问题通常可以通过动态规划(Dynamic Programming, DP)来解决。动态规划的核心思想是通过将问题分解为子问题来递推求解,从而避免重复计算。

具体来说,我们可以构建一个二维数组 dp,其中 dp[i][j] 表示前 i 个物品中,选择总重量不超过 j 的最大价值。通过遍历所有可能的物品和容量状态,我们可以最终得到答案。

动态规划转移方程

  • 状态定义dp[i][j] 表示前 i 个物品中,背包容量不超过 j 时的最大价值。

  • 状态转移

    • 如果第 i 个物品的重量大于当前背包容量 j,则不能选择该物品,状态转移为 dp[i][j] = dp[i-1][j]
    • 如果第 i 个物品的重量小于等于背包容量 j,我们可以选择该物品,也可以不选择。此时,状态转移为: 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 - \text{weights}[i-1]] + \text{values}[i-1])

    其中,dp[i-1][j] 表示不选第 i 个物品的情况,dp[i-1][j - weights[i-1]] + values[i-1] 表示选择第 i 个物品的情况。

代码实现

下面是一个用 Java 实现的解法:

public class Main {
    public static int solution(int n, int[] weights, int[] values, int m) {
        // 创建一个二维数组 dp,其中 dp[i][j] 表示前 i 个物品中选择总重量不超过 j 的最大价值。
        int[][] dp = new int[n + 1][m + 1];

        // 遍历所有状态
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j <= m; j++) {
                if (weights[i - 1] > j) {
                    // 如果第 i 个物品的重量大于当前背包的容量,则不能选这个物品
                    dp[i][j] = dp[i - 1][j];
                } else {
                    // 可以选择第 i 个物品,也可以不选,取两者之间的最大值
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weights[i - 1]] + values[i - 1]);
                }
            }
        }

        // 返回最终结果,即前 n 个物品中选择总重量不超过 capacity 的最大价值
        return dp[n][m];
    }

    public static void main(String[] args) {
        System.out.println(solution(3, new int[] { 2, 1, 3 }, new int[] { 4, 2, 3 }, 3) == 6);
        System.out.println(solution(4, new int[] { 1, 2, 3, 2 }, new int[] { 10, 20, 30, 40 }, 5) == 70);
        System.out.println(solution(2, new int[] { 1, 4 }, new int[] { 5, 10 }, 4) == 10);
    }
}

代码分析

  1. 初始化 dp 数组

    • dp[i][j] 用于存储前 i 个物品在背包容量为 j 时的最大价值。
    • 初始化时,所有的 dp[i][j] 为 0,表示没有选择任何物品时,背包的价值为 0。
  2. 状态转移

    • 遍历每个物品 i 和背包容量 j,判断当前物品是否可以放入背包。
    • 如果物品 i 的重量 weights[i-1] 大于当前背包容量 j,则不能放入该物品,状态转移为 dp[i][j] = dp[i-1][j]
    • 如果可以放入该物品,则取不放入和放入该物品两种情况中的较大值。
  3. 最终结果

    • dp[n][m] 即为最终结果,表示在前 n 个物品中,选择总重量不超过 m 的物品组合的最大价值。

复杂度分析

  • 时间复杂度:我们使用了两层循环,外层循环遍历物品,内层循环遍历背包容量。因此,时间复杂度为 O(n * m),其中 n 是物品的数量,m 是背包的容量。
  • 空间复杂度:由于我们使用了一个二维数组 dp 来存储状态,因此空间复杂度为 O(n * m)

测试案例

我们可以使用以下测试案例来验证算法的正确性:

  1. 测试案例 1

    • 输入:n = 3, weights = [2, 1, 3], values = [4, 2, 3], m = 3
    • 输出:6
  2. 测试案例 2

    • 输入:n = 4, weights = [1, 2, 3, 2], values = [10, 20, 30, 40], m = 5
    • 输出:70
  3. 测试案例 3

    • 输入:n = 2, weights = [1, 4], values = [5, 10], m = 4
    • 输出:10

总结

通过动态规划的方式,我们成功地解决了背包问题。通过状态转移,我们能够在每一步做出是否选择某个物品的决策,最终得到最大价值。在实际应用中,背包问题不仅限于旅行,它在物流调度、资源配置等领域都有广泛应用。希望通过本篇文章,读者能更好地理解背包问题的解法及其在实际中的应用。