背包 DP:容量限制下的选择模型

系统整理 0-1 背包、完全背包、多重背包、分组背包和方案数问题,重点区分遍历顺序。

已发布文章计算机基础入门6 分钟阅读发布于 2026年9月1日更新于 2026年9月15日
文章目录
  1. 背包类型地图
  2. 0-1 背包
  3. 完全背包
  4. 方案数问题
  5. 多重背包
  6. 分组背包
  7. 常见错误
  8. 工程场景
  9. 面试题
  10. 小结
知识目录数据结构与算法:从基础到工程实践31 / 77
LOCAL NOTE高亮并记笔记
0 / 1000 · 只保存在这台设备
READER NOTES背包 DP:容量限制下的选择模型》的本机笔记

第一次碰到“背包 DP:容量限制下的选择模型”,我以为把定义和代码记下来就算学会了。真正动手时才暴露出问题:背包问题里的一维优化曾让我非常困惑,同一段循环只是容量方向不同,为什么就变成了完全不同的选择次数。这次重学我刻意放慢了速度,先看它为什么出现,再看规则怎样工作,最后才落到实现。

背包问题是动态规划里最经典的一类模型。它抽象的是:在容量或资源限制下,从一组物品里做选择,让价值最大、成本最小或方案数符合要求。

背包不只是算法题。工程里的预算分配、资源调度、组合选择、容量规划,都能看到背包思想。

背包类型地图

背包 DP 类型

图:不同背包的区别主要在“每个物品能选几次”

常见类型:

类型 物品限制 一维遍历方向
0-1 背包 每个物品最多选一次 容量倒序
完全背包 每个物品可以选无限次 容量正序
多重背包 每个物品有固定数量 二进制拆分或单调队列优化
分组背包 每组最多选一个 先枚举组

0-1 背包

状态定义:dp[i][w] 表示前 i 个物品、容量为 w 时能得到的最大价值。

转移:

不选第 i 个:dp[i-1][w]
选第 i 个:dp[i-1][w-weight[i]] + value[i]

二维写法:

int knapsack01(int[] weight, int[] value, int cap) {
    int n = weight.length;
    int[][] dp = new int[n + 1][cap + 1];
    for (int i = 1; i <= n; i++) {
        for (int w = 0; w <= cap; w++) {
            dp[i][w] = dp[i - 1][w];
            if (w >= weight[i - 1]) {
                dp[i][w] = Math.max(dp[i][w],
                        dp[i - 1][w - weight[i - 1]] + value[i - 1]);
            }
        }
    }
    return dp[n][cap];
}

一维压缩:

int knapsack01Compressed(int[] weight, int[] value, int cap) {
    int[] dp = new int[cap + 1];
    for (int i = 0; i < weight.length; i++) {
        for (int w = cap; w >= weight[i]; w--) {
            dp[w] = Math.max(dp[w], dp[w - weight[i]] + value[i]);
        }
    }
    return dp[cap];
}

为什么倒序?因为每个物品只能选一次。倒序可以防止当前轮刚更新的 dp[w-weight] 被同一个物品再次使用。

完全背包

完全背包每个物品可以选多次,所以一维容量要正序。

int completeKnapsack(int[] weight, int[] value, int cap) {
    int[] dp = new int[cap + 1];
    for (int i = 0; i < weight.length; i++) {
        for (int w = weight[i]; w <= cap; w++) {
            dp[w] = Math.max(dp[w], dp[w - weight[i]] + value[i]);
        }
    }
    return dp[cap];
}

正序意味着 dp[w-weight[i]] 可以来自当前物品本轮更新,也就是允许重复选择。

方案数问题

背包不只求最大价值,也可以求方案数。

例如硬币兑换组合数:

int change(int amount, int[] coins) {
    int[] dp = new int[amount + 1];
    dp[0] = 1;
    for (int coin : coins) {
        for (int x = coin; x <= amount; x++) {
            dp[x] += dp[x - coin];
        }
    }
    return dp[amount];
}

先枚举硬币,再枚举金额,得到的是组合数。如果顺序反过来,可能变成排列数。这是背包题里非常常见的坑。

多重背包

多重背包每个物品有数量限制。朴素做法是枚举选择几个:

dp[w] = max(dp[w], dp[w-k*weight] + k*value)

如果数量很大,可以把数量拆成 1,2,4,8... 的若干组,转成多个 0-1 物品,这叫二进制拆分。

分组背包

分组背包要求每组最多选一个。遍历顺序通常是:

枚举组
  倒序枚举容量
    枚举组内物品

它适合“多个方案互斥选择”的场景。

常见错误

  • 0-1 背包一维压缩容量正序,导致物品被重复选。
  • 完全背包容量倒序,导致无法重复选。
  • 组合数和排列数循环顺序混淆。
  • dp[0] 初始化错误。
  • 容量含义不清楚,是重量、金额、时间还是次数。
  • 求最小值时忘记初始化为无穷大。

工程场景

  • 预算有限时选择收益最大的项目组合。
  • 服务器容量有限时选择任务部署组合。
  • 优惠券、满减、组合套餐。
  • 时间有限时规划学习任务。
  • 推荐系统里控制曝光资源分配。

工程里的背包往往不止一个约束,比如预算、时间、人力、风险同时存在,这会变成多维背包或整数规划问题。

面试题

  1. 0-1 背包为什么容量要倒序?
  2. 完全背包为什么容量可以正序?
  3. 组合数和排列数的循环顺序有什么区别?
  4. 多重背包如何优化?
  5. 背包问题在工程里有哪些对应场景?

小结

背包 DP 的本质是容量约束下的选择。最关键的是搞清楚“每个物品能选几次”和“当前状态依赖上一轮还是本轮”。只要这个想清楚,0-1、完全、多重、分组背包就不会混成一锅粥。

JARVIS · 当前文章

有哪里没看懂?可以只问这篇。

Jarvis 会限定在《背包 DP:容量限制下的选择模型》及其公开关联内容中检索,并把引用定位回原文章节。

JARVIS / ARTICLE针对《背包 DP:容量限制下的选择模型》提问
当前范围背包 DP:容量限制下的选择模型不会悄悄扩大到全站
0 / 1000

准备好了。当前只会围绕背包 DP:容量限制下的选择模型回答。

READER SIGNAL

这篇内容对你有帮助吗?

不需要登录。你的反馈会直接进入作者待处理列表。