背包问题中的组合数与排列数的区别非常明确。这取决于物品和容量遍历的顺序:
组合数的代码逻辑:
for (int i = 0; i < coins.size(); i++) {
// 遍历物品
for (int j = coins[i]; j <= amount; j++) {
// 遍历背包容量
dp[j] += dp[j - coins[i]];
}
}
解析:
- 首先遍历物品
coins[i]
,然后在该物品的基础上更新背包容量j
。 - 这意味着在计算
dp[j]
时,物品coins[i]
是先加入计算的。 - 因为每次只在当前物品基础上更新容量,所以相当于 先固定物品,再考虑容量,这只能产生唯一的排列方式,不能重排顺序。
- 结果就是 组合数,如
{1, 5}
和{5, 1}
被视为同一种情况。
排列数的代码逻辑:
for (int j = 0; j <= amount; j++) {
// 遍历背包容量
for (int i = 0; i < coins.size(); i++) {
// 遍历物品
if (j - coins[i] >= 0)
dp[j] += dp[j - coins[i]];
}
}
解析:
- 这段代码首先遍历背包容量
j
,然后遍历所有的物品coins[i]
。 - 这意味着在计算
dp[j]
时,每个物品都可能以不同的顺序加入计算,即 先固定容量,再尝试不同物品。 - 因此,不同物品的排列顺序都会影响结果,像
{1, 5}
和{5, 1}
会被分别计算。 - 结果就是 排列数,因为顺序不同的情况会被视为不同的解法。
总结:
- 组合数 是不关心物品加入的顺序的,只要最终结果是相同的,那么
{1, 5}
和{5, 1}
会视为同一种组合。 - 排列数 则考虑顺序,不同的物品加入顺序会视为不同的排列,所以
{1, 5}
和{5, 1}
是不同的解。
这种区别在背包问题中的重要性在于你想解决的是 组合问题(只关心物品组合的不同)还是 排列问题(关心顺序)。
你可以通过在循环中添加打印语句来查看 dp
数组在每次迭代时的动态变化。下面是实现打印 dp
数组动态变化的代码示例:
组合数的 dp
数组动态变化:
#include <iostream>
#include <vector>
using namespace std;
void printDP(const vector<int>& dp) {
for (int val : dp) {
cout << val << " ";
}
cout << endl;
}
int main() {
int amount = 10;
vector<int> coins = {1, 5};
vector<int> dp(amount + 1, 0);
dp[0] = 1; // 初始状态:容量为0时有1种方案(不选任何物品)
cout << "组合数的动态变化:" << endl;
for (int i = 0; i < coins.size(); i++) { // 遍历物品
for (int j = coins[i]; j <= amount; j++) { // 遍历背包容量
dp[j] += dp[j - coins[i]];
printDP(dp); // 打印当前 dp 数组的状态
}
}
return 0;
}
排列数的 dp
数组动态变化:
#include <iostream>
#include <vector>
using namespace std;
void printDP(const vector<int>& dp) {
for (int val : dp) {
cout << val << " ";
}
cout << endl;
}
int main() {
int amount = 10;
vector<int> coins = {1, 5};
vector<int> dp(amount + 1, 0);
dp[0] = 1; // 初始状态:容量为0时有1种方案(不选任何物品)
cout << "排列数的动态变化:" << endl;
for (int j = 0; j <= amount; j++) { // 遍历背包容量
for (int i = 0; i < coins.size(); i++) { // 遍历物品
if (j - coins[i] >= 0) dp[j] += dp[j - coins[i]];
printDP(dp); // 打印当前 dp 数组的状态
}
}
return 0;
}
输出示例:
组合数的动态变化:
组合数的动态变化:
1 1 0 0 0 0 0 0 0 0 0
1 1 1 0 0 0 0 0 0 0 0
1 1 1 1 0 0 0 0 0 0 0
1 1 1 1 1 0 0 0 0 0 0
1 1 1 1 1 1 0 0 0 0 0
1 1 1 1 1 1 1 0 0 0 0
1 1 1 1 1 1 1 1 0 0 0
1 1 1 1 1 1 1 1 1 0 0
1 1 1 1 1 1 1 1 1 1 0
1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 2 1 1 1 1 1
1 1 1 1 1 2 2 1 1 1 1
1 1 1 1 1 2 2 2 1 1 1
1 1 1 1 1 2 2 2 2 1 1
1 1 1 1 1 2 2 2 2 2 1
排列数的动态变化:
排列数的动态变化:
1 0 0 0 0 0 0 0 0 0 0
1 1 0 0 0 0 0 0 0 0 0
1 1 1 0 0 0 0 0 0 0 0
1 1 1 1 0 0 0 0 0 0 0
1 1 1 1 1 0 0 0 0 0 0
1 1 1 1 1 1 0 0 0 0 0
1 1 1 1 1 1 1 0 0 0 0
1 1 1 1 1 1 1 1 0 0 0
1 1 1 1 1 1 1 1 1 0 0
1 1 1 1 1 1 1 1 1 1 0
1 2 1 1 1 1 1 1 1 1 1
1 2 3 1 1 1 1 1 1 1 1
1 2 3 4 1 1 1 1 1 1 1
1 2 3 4 5 1 1 1 1 1 1
1 2 3 4 5 6 1 1 1 1 1
通过这些动态打印,你可以清晰地看到 dp
数组如何随着迭代逐步更新,组合数和排列数的变化也会一目了然。