完全背包和0-1背包问题

70 阅读3分钟

一、0-1 背包问题

📖 题目定义

n 个物品,每个物品只能用 一次,第 i 个物品有:

  • 价值 value[i]
  • 重量 weight[i]
    背包最大容量为 W
    求在不超过背包容量的情况下,能放入的 最大价值

🔎 解题思路

  1. 状态定义
    dp[i][j] = 前 i 件物品在容量 j 下的最大价值。

  2. 状态转移

    • 不选第 i 个物品:dp[i][j] = dp[i-1][j]
    • 选第 i 个物品:dp[i][j] = dp[i-1][j-weight[i]] + value[i]
    • 取最大值。
    dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i])
    
  3. 初始化

    • dp[0][..] = 0 (没有物品时价值为 0)。
    • dp[..][0] = 0 (容量为 0 时价值为 0)。
  4. 关键点

    • 为了避免一件物品被用多次,内层容量循环要倒序(一维压缩版时)。

✅ Java 代码(二维 DP + 一维优化)

class Solution01Knapsack {
    public int knapsack01(int[] weights, int[] values, int W) {
        int n = weights.length;
        int[][] dp = new int[n + 1][W + 1];

        // 二维DP模板
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j <= W; j++) {
                dp[i][j] = dp[i - 1][j]; // 不选
                if (j >= weights[i - 1]) {
                    dp[i][j] = Math.max(dp[i][j],
                        dp[i - 1][j - weights[i - 1]] + values[i - 1]);
                }
            }
        }
        return dp[n][W];
    }

    // 一维优化版
    public int knapsack01Optimized(int[] weights, int[] values, int W) {
        int n = weights.length;
        int[] dp = new int[W + 1];

        for (int i = 0; i < n; i++) {
            for (int j = W; j >= weights[i]; j--) { // 倒序避免重复选
                dp[j] = Math.max(dp[j], dp[j - weights[i]] + values[i]);
            }
        }
        return dp[W];
    }
}

二、完全背包问题

📖 题目定义

n 个物品,每个物品可以用 无限次,第 i 个物品有:

  • 价值 value[i]
  • 重量 weight[i]
    背包最大容量为 W
    求在不超过背包容量的情况下,能放入的 最大价值

🔎 解题思路

  1. 状态定义
    dp[i][j] = 前 i 种物品在容量 j 下的最大价值。

  2. 状态转移

    • 不选第 i 个物品:dp[i][j] = dp[i-1][j]
    • 选第 i 个物品(可重复选):dp[i][j] = dp[i][j-weight[i]] + value[i]
    • 取最大值。
    dp[i][j] = max(dp[i-1][j], dp[i][j-weight[i]] + value[i])
    

    注意这里和 0-1 背包的区别:是从 dp[i][..] 转移,而不是 dp[i-1][..]

  3. 关键点

    • 为了允许多次使用物品,内层容量循环要正序(一维压缩版时)。

✅ Java 代码(二维 DP + 一维优化)

class SolutionCompleteKnapsack {
    public int completeKnapsack(int[] weights, int[] values, int W) {
        int n = weights.length;
        int[][] dp = new int[n + 1][W + 1];

        // 二维DP模板
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j <= W; j++) {
                dp[i][j] = dp[i - 1][j]; // 不选
                if (j >= weights[i - 1]) {
                    dp[i][j] = Math.max(dp[i][j],
                        dp[i][j - weights[i - 1]] + values[i - 1]);
                }
            }
        }
        return dp[n][W];
    }

    // 一维优化版
    public int completeKnapsackOptimized(int[] weights, int[] values, int W) {
        int n = weights.length;
        int[] dp = new int[W + 1];

        for (int i = 0; i < n; i++) {
            for (int j = weights[i]; j <= W; j++) { // 正序允许重复使用
                dp[j] = Math.max(dp[j], dp[j - weights[i]] + values[i]);
            }
        }
        return dp[W];
    }
}

三、0-1 背包 vs 完全背包(对比表)

特性0-1 背包完全背包
每件物品次数只能选 0 或 1 次可无限次使用
状态转移dp[i][j] = max(dp[i-1][j], dp[i-1][j-w] + v)dp[i][j] = max(dp[i-1][j], dp[i][j-w] + v)
一维优化循环倒序 (避免重复使用)正序 (允许重复使用)

✅ 总结:

  • 0-1 背包:物品只能用一次 → 倒序遍历容量
  • 完全背包:物品可无限次使用 → 正序遍历容量

要不要我帮你再用「零钱兑换」这个经典题(LeetCode 322)把它和 完全背包 的关系对应起来(换个说法就是“求最少硬币数”的完全背包)?