代码随想录算法训练营第三十二天| 完全背包, 518. 零钱兑换 II, 377. 组合总和 Ⅳ, 70. 爬楼梯 (进阶)

今天是动态规划学习的第五天,主要学习了多重背包问题的原理以及应用,包括多重背包应用中的排列和组合问题的区分。

完全背包

题目链接:52. 携带研究材料(第七期模拟笔试) (kamacoder.com)

完全背包的含义是在01背包的基础上去掉物品只能选取一次的限制,即对于每件物品可以选取无限次,进而求解装满背包的最大价值。多重背包与01背包的主要区别在于递归公式上我们先来回顾一下01背包的递归公式:

for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    }
}

换做是多重背包问题,我们只需要修改背包容量的遍历顺序,正序遍历背包容量即可。具体代码实现如下所示

// 先遍历物品,再遍历背包
for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = weight[i]; j <= bagWeight ; j++) { // 遍历背包容量
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

    }
}

还有一点值得注意,对于多重背包问题,外层循环和内层循环的顺序是可以颠倒的,因为dp[j]的数值是根据前面的数值得到的,不管外层循环是背包容量,内层循环是物品;还是外层循环是物品,内层循环是背包容量,dp[j]前面的数值都是正确更新的,所以这个遍历顺序是无关紧要的。这一点需要与01背包进行比较和区别。具体代码实现如下所示:

#include<iostream>
#include<vector>
using namespace std;
int main()
{
    int n,v;
    scanf("%d%d",&n,&v);
    int weight[n];
    int val[n];
    for(int i=0;i<n;i++) scanf("%d %d",&weight[i],&val[i]);
    vector<int> dp(v+1,0);
    for(int i=0;i<n;i++)
    {
        for(int j=weight[i];j<=v;j++)
        {
            dp[j]=max(dp[j],dp[j-weight[i]]+val[i]);
        }
    }
    printf("%d",dp[v]);
}

518. 零钱兑换 II

题目链接:518. 零钱兑换 II - 力扣(LeetCode)

这个题目是多重背包的一种应用,也牵扯出一个问题,排列和组合的区别。

首先题目是给定一个总金额和不同金额的集合,求解有多少种凑成总金额的方式。抽象出来,其实就是多重背包问题的背景下装满背包的方法有多少种,在01背包问题中我们也遇到了这样的问题。迭代公式如下

dp[j]+=dp[j-coins[i]];

所以我们只需要修改背包容量的遍历顺序,改为正序遍历即可。具体代码实现如下所示:
 

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        vector<int> dp(amount+5,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];
    }
};

我做这道题目是很快AC了的,但是看了题解后有了一些新的理解。那就是排列和组合的区别。

组合是不强调顺序的,{1,5}和{5,1}是同一种方法;但是排列是强调顺序的,{1,5}和{5,1}是两种方法。所以,这个题目是不能修改外层循环和内层循环的内容的,外层循环只能是遍历物品,内层循环只能是遍历背包容量,这统计的是组合数;如果颠倒顺序,外层循环是遍历容量,内层循环是遍历物品,统计的就是排列数。我自己写了一段打印的代码,结果可以反映出这一现象。

组合数计算过程(外层遍历物品):
1 0 0 0 0 0
1 1 0 0 0 0
1 1 1 0 0 0
1 1 1 1 0 0
1 1 1 1 1 0
1 1 1 1 1 1
1 1 1 1 1 1
1 1 1 1 1 1
1 1 2 1 1 1
1 1 2 2 1 1
1 1 2 2 3 1
1 1 2 2 3 3
1 1 2 2 3 3
1 1 2 2 3 3
1 1 2 2 3 3
1 1 2 2 3 3
1 1 2 2 3 3
1 1 2 2 3 4
-------------------------
排列数计算过程(外层遍历背包容量):
1 0 0 0 0 0
1 0 0 0 0 0
1 0 0 0 0 0
1 1 0 0 0 0
1 1 0 0 0 0
1 1 0 0 0 0
1 1 1 0 0 0
1 1 2 0 0 0
1 1 2 0 0 0
1 1 2 2 0 0
1 1 2 3 0 0
1 1 2 3 0 0
1 1 2 3 3 0
1 1 2 3 5 0
1 1 2 3 5 0
1 1 2 3 5 5
1 1 2 3 5 8
1 1 2 3 5 9

所以在遇到多重背包问题,求解装满背包的方法数时,我们需要区分改变顺序是否是两种不同的方法,以确定不同的遍历顺序。

377. 组合总和 Ⅳ

题目链接:377. 组合总和 Ⅳ - 力扣(LeetCode)

这个题目就是一个典型的排列问题,(1,1,2),(1,2,1),(2,1,1)属于不同的方法。所以,我们应该在外层循环遍历背包容量,内层循环遍历物品。具体代码如下所示:
 

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<unsigned int> dp(target+10,0);
        dp[0]=1;
        for(int i=1;i<=target;i++)
        {
            for(int j=0;j<nums.size();j++)
            {
                if(i>=nums[j]) dp[i]+=dp[i-nums[j]];
            }
            
        }
        return dp[target];
    }
};

70. 爬楼梯 (进阶)

题目链接:57. 爬楼梯(第八期模拟笔试) (kamacoder.com)

这个题也是一个排列的问题,所以也是先遍历背包容量,在遍历物品件数;在这个题目中就是先遍历最大阶数,内层循环遍历每次能走的步数。具体代码实现如下所示:
 

#include<iostream>
#include<vector>
using namespace std;
int main()
{
    int m,n;
    cin>>n>>m;
    vector<int> dp(n+10,0);
    dp[0]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if(i>=j) dp[i]+=dp[i-j];
        }
    }
    printf("%d",dp[n]);
}

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值