动态规划完全背包问题详解 - itcharge/LeetCode-Py项目解析
什么是完全背包问题
完全背包问题是经典的动态规划问题之一,与0-1背包问题类似但有重要区别。问题描述如下:
给定n种物品和一个容量为W的背包,第i种物品的重量为weight[i],价值为value[i],每种物品的数量无限。求解在不超过背包容量的情况下,能够装入背包的最大价值总和。
完全背包与0-1背包的区别
完全背包与0-1背包的核心区别在于物品的可选数量:
- 0-1背包:每种物品只能选0件或1件
- 完全背包:每种物品可以选择0件或任意多件(只要不超过背包容量)
基本解法思路
方法一:三维循环暴力解法
最直观的思路是枚举每种物品的选择数量:
- 定义状态dp[i][w]表示前i种物品装入容量为w的背包的最大价值
- 对于每种物品i,枚举可能的选取数量k(0 ≤ k ≤ w/weight[i-1])
- 状态转移方程: dp[i][w] = max(dp[i-1][w-kweight[i-1]] + kvalue[i-1]),对所有可能的k
这种方法虽然直观,但时间复杂度高达O(nW²),效率较低。
方法二:优化状态转移方程
通过数学推导可以优化状态转移方程:
- 原始状态转移方程展开后可以发现规律
- 优化后的状态转移方程: dp[i][w] = max(dp[i-1][w], dp[i][w-weight[i-1]] + value[i-1])
- 当w < weight[i-1]时,dp[i][w] = dp[i-1][w]
这种优化将时间复杂度降为O(nW),空间复杂度仍为O(nW)。
方法三:滚动数组优化空间
进一步观察可以发现,状态转移只依赖于当前行和上一行的部分数据,因此可以使用一维数组优化空间:
- 定义dp[w]表示容量为w的背包能装的最大价值
- 状态转移方程: dp[w] = max(dp[w], dp[w-weight[i-1]] + value[i-1])
- 需要正序遍历背包容量(与0-1背包的逆序遍历不同)
这种方法将空间复杂度优化到O(W),是实际应用中最常用的解法。
关键点解析
-
正序与逆序遍历的区别:
- 完全背包使用正序遍历,因为可以重复选取同一物品
- 0-1背包使用逆序遍历,确保物品只被选取一次
-
状态转移的理解:
- dp[i][w] = max(不选当前物品,选当前物品)
- 选当前物品时,由于可以重复选,所以是从dp[i][w-weight[i-1]]转移而来
-
边界条件处理:
- 容量为0时,价值为0
- 物品数量为0时,价值为0
代码实现
以下是滚动数组优化的Python实现:
def completePack(weight, value, W):
dp = [0] * (W + 1)
for i in range(len(weight)):
for w in range(weight[i], W + 1):
dp[w] = max(dp[w], dp[w - weight[i]] + value[i])
return dp[W]
复杂度分析
- 时间复杂度:O(nW),n为物品种类,W为背包容量
- 空间复杂度:O(W),只需一维数组存储状态
实际应用
完全背包问题有许多实际应用场景,例如:
- 零钱兑换问题(硬币无限供应)
- 物品切割问题(原材料可以无限切割)
- 资源分配问题(资源可以重复使用)
理解完全背包问题的解法思路,对于解决这类实际问题非常有帮助。
总结
完全背包问题是动态规划中的经典问题,通过本文的三种解法可以看出动态规划问题的优化思路:从暴力解法开始,通过寻找规律优化状态转移方程,最后通过空间优化减少内存使用。掌握这种逐步优化的思路,对于解决其他动态规划问题也大有裨益。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考