背包 DP

背包 DP 总览:01 背包、完全背包、至多装/恰好装/至少装、最值/可行性/方案数。

#resource / algorithm #type / concept #status / growing #algo / dp

[!info] related notes 算法面试题型 MOC 动态规划 分割等和子集 零钱兑换 零钱兑换 II 目标和

背包 DP

一句话

从若干物品中做选择,满足某种容量限制,求最优值 / 方案数 / 是否可行。

一页式速查表

类型是否可重复选常见状态枚举顺序代表题
01 背包最值dp[j] / dp[i][j]容量倒序[[partition-equal-subset-sum分割等和子集]]
完全背包最值dp[j] / dp[i][j]容量正序[[coin-change零钱兑换]]
完全背包方案数dp[j]物品外层、容量正序[[coin-change-ii零钱兑换 II]]
01 背包方案数dp[j]容量倒序[[target-sum目标和]]
恰好装可行性视题而定dp[j] = bool看题型[[partition-equal-subset-sum分割等和子集]]

识别信号

  • 给你一堆物品,每个物品有“体积 / 重量 / 代价”
  • 有容量限制、目标和、预算限制
  • 每个物品最多用一次 / 可以无限次使用

01 背包

每个物品最多选一次。

状态

dp[i][j] = 从前 i 个物品中选,容量不超过 j 时的最优值 / 方案数

转移

  • 不选第 i 个:dp[i - 1][j]
  • 选第 i 个:dp[i - 1][j - w[i]] + v[i]

最优值型:

dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i])

一维优化模板

for (let i = 0; i < n; i++) {
    for (let j = m; j >= w[i]; j--) {
        dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]);
    }
}

倒序原因:防止同一轮里同一个物品被重复使用。

完全背包

每个物品可以选无限次。

转移

因为选了当前物品后还能继续选它,所以来自当前行:

dp[i][j] = max(dp[i - 1][j], dp[i][j - w[i]] + v[i])

一维优化模板

for (let i = 0; i < n; i++) {
    for (let j = w[i]; j <= m; j++) {
        dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]);
    }
}

正序原因:允许当前物品在同一轮被重复使用。

三种高频问法

1. 至多装

不要求刚好装满。

2. 恰好装

必须刚好凑出目标。

初始化往往是关键:

  • 最大值型:dp[0] = 0,其他设 -INF
  • 最小值型:dp[0] = 0,其他设 INF
  • 可行性:dp[0] = true

3. 至少装

达到至少某个值即可。常见做法是最后枚举 j >= target

样例 1:分割等和子集(01 背包可行性)

题目:分割等和子集

题意:数组能不能选出一些数,使其和等于总和的一半。

状态

dp[j] 表示是否能恰好凑出和 j

转移

dp[j] = dp[j] || dp[j - nums[i]]

容量倒序枚举,因为每个数只能用一次。

样例 2:零钱兑换(完全背包最小值)

题目:零钱兑换

题意:硬币可重复使用,问凑成金额 amount 的最少硬币数。

状态

dp[j] 表示凑出金额 j 的最少硬币数。

转移

dp[j] = min(dp[j], dp[j - coin] + 1)

容量正序,因为硬币能重复使用。

高频坑

  • 01 背包和完全背包把顺序写反
  • 不清楚状态里存的是最值、方案数还是可行性
  • 恰好装题目初始化乱写,导致不可达状态参与转移
  • 组合和排列搞混:枚举物品在外还是枚举容量在外,含义不同

模板与代表题

创建于 2026/3/18 更新于 2026/5/27