三、总结:遍历顺序的核心逻辑
| 场景 | 遍历顺序要求 | 核心原因 |
|---|---|---|
| 01背包(二维dp) | 先物品/先容量均可,容量正序 | 依赖上一行数据,无覆盖问题 |
| 01背包(一维dp) | 先物品,后容量,容量倒序 | 避免覆盖上一行数据,保证物品只选一次 |
| 完全背包(最大价值) | 先物品/先容量均可,容量正序 | 允许覆盖当前行数据,实现物品重复选择 |
| 完全背包(组合数) | 外层物品,内层容量 | 固定物品顺序,避免重复计数 |
| 完全背包(排列数) | 外层容量,内层物品 | 每个容量尝试所有物品作为最后一步,生成不同顺序的方案 |
本质上,遍历顺序的设计都是为了匹配问题的约束(选一次/选多次)和目标(价值/方案数/组合/排列),核心是理解「状态转移时依赖的数据是否会被提前覆盖」。
https://niumacode.com/article/20
完全背包即在0-1背包的基础上,可以重复选择
不同点:
- 一个是i-1和i
- 还有纯完全内外循环随便(背包容量和weight),但有时候必须是其中一种,比如:https://kamacoder.com/problempage.php?pid=1067
- 循环的起始点不一定,可以根据题意,再在循环里做判断条件递归
- 如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。
即在遍历i的每层中,j扩大到>=weights[i]时,
- 在01时,
价值最大化max(两个选择):dp[i][j] = max(dp[i - 1][j], dp[i-1][j - weight[i]] + value[i]);
- 不选i,dp[i][j]=dp[i-1][j]
- 选i,dp[i][j]=dp[i-1][j - weight[i]] + valuei
最大组合数:dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]]
- 而在完全背包,即使在j>=weights[i]后,空出一个weights[i]后,前面仍可能有i,故i不变:
价值最大化:dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]);
最大组合数:dp[i][j] = dp[i - 1][j]+dp[i][j - weight[i]] ;
组合树的递推与value无关
初始化
在第一列都为0,装不下
第一行则正序遍历,如果能放下就一直装物品0
其他可以直接初始化为0.
// 初始化 价值最大化dp
vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));
for (int j = weight[0]; j <= bagWeight; j++) {
dp[0][j] = dp[0][j - weight[0]] + value[0];
}
循环遍历:对于纯完全背包问题,其for循环的先后循环是可以颠倒的!
但如果题目稍稍有点变化,就会体现在遍历顺序上。
如果问装满背包有几种方式的话? 那么两个for循环的先后顺序就有很大区别了
先遍历物品再遍历背包:
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量
if (j - weight[i] >= 0) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
最大组合数例子:https://leetcode.cn/problems/coin-change-ii/
class Solution {
public:
//多重背包的最大组合数问题,用加法且不加value
//故递推公式为dp[i][j]=dp[i-1][j]+(dp[i][j-weight[i]])
//简化为一维数组则为dp[j]+=dp[j-weights[i]]
int change(int amount, vector<int>& coins) {
vector<uint64_t> dp(amount+1,0);
dp[0]=1;//即什么都不选也是一种方案
for(int i=0;i<coins.size();i++){
for(int j=coins[i];j<=amount;j++){
dp[j]+=dp[j-coins[i]];
}
}
return dp[amount];
}
};
浙公网安备 33010602011771号