背包问题中的组合数与排列数的区别

背包问题中的组合数与排列数的区别非常明确。这取决于物品和容量遍历的顺序:

组合数的代码逻辑:

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 数组如何随着迭代逐步更新,组合数和排列数的变化也会一目了然。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值