代码随想录算法训练营第三十三天|动态规划:完全背包理论基础、518.零钱兑换II、377. 组合总和 Ⅳ、70. 爬楼梯(进阶版)

动态规划:完全背包理论基础

完全背包就是每个物品可以使用无数次,最后背包可以放的物品最大价值是多少。

01背包中:

for(i=0; i<weight.size; i++){
    for(j=0; j>=weight[i]; j--){
        dp[j] = max(dp[j], dp[j-weight[i]+value[i]);
    }
}

而完全背包,里面的for循环会变成:

正序遍历,让物品的选用多次。

for(j=weight[i]; j<=bagsize;j++)//物品数量不受限制

01背包中二维dp数组的两个for遍历的先后循序是可以颠倒了,一维dp数组的两个for循环先后循序一定是先遍历物品,再遍历背包容量。

在完全背包中,对于一维dp数组来说,其实两个for循环嵌套顺序是无所谓的!

先遍历物品再遍历背包。和先遍历背包再遍历物品的结果是一样的。

先遍历物品后遍历背包:

先遍历背包后遍历物品:

void test_CompletePack() {
    vector<int> weight = {1, 3, 4};
    vector<int> value = {15, 20, 30};
    int bagWeight = 4;
    vector<int> dp(bagWeight + 1, 0);
    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]);
        }
    }
    cout << dp[bagWeight] << endl;
}
int main() {
    test_CompletePack();
}

518.零钱兑换II

给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个

dp[j]:凑成总金额j的货币组合数为dp[j]

dp[j] 就是所有的dp[j - coins[i]](考虑coins[i]的情况)相加。

注意这里,如果先遍历物品再遍历背包,是组合;但是如果先遍历背包再遍历物品,是排列,因为{1,5}和{5,1}算不同的方法了。

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        vector<int> 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]]; //因为coins的重量均为1,所以直接加上
            }
        }
        return dp[amount];
    }
};
  • 时间复杂度: O(mn),其中 m 是amount,n 是 coins 的长度
  • 空间复杂度: O(m)

377. 组合总和 Ⅳ

给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。

本题题目描述说是求组合,但又说是可以元素相同顺序不同的组合算两个组合,其实就是求排列!

dp[i]: 凑成目标正整数为i的排列个数为dp[i]

求装满背包有几种方法,递推公式一般都是dp[i] += dp[i - nums[j]];

因为递推公式dp[i] += dp[i - nums[j]]的缘故,dp[0]要初始化为1,这样递归其他dp[i]的时候才会有数值基础。

如果求组合数就是外层for循环遍历物品,内层for遍历背包

如果求排列数就是外层for遍历背包,内层for循环遍历物品

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<int> dp(target+1, 0);
        dp[0] = 1;
        for(int i = 0; i<=target; i++){
            for(int j = 0; j<nums.size(); j++){
                if(i >= nums[j] && dp[i] < INT_MAX - dp[i-nums[j]]){
                    dp[i] += dp[i-nums[j]];
                }
            }
        }
        return dp[target];
    }
};
  • 时间复杂度: O(target * n),其中 n 为 nums 的长度
  • 空间复杂度: O(target)

C++测试用例有两个数相加超过int的数据,所以需要在if里加上dp[i] < INT_MAX - dp[i - num]。

70. 爬楼梯(进阶版)

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬至多m (1 <= m < n)个台阶。你有多少种不同的方法可以爬到楼顶呢?

dp[i]:爬到有i个台阶的楼顶,有dp[i]种方法

求装满背包有几种方法,递推公式一般都是dp[i] += dp[i - nums[j]];

本题呢,dp[i]有几种来源,dp[i - 1],dp[i - 2],dp[i - 3] 等等,即:dp[i - j]

那么递推公式为:dp[i] += dp[i - j]

既然递归公式是 dp[i] += dp[i - j],那么dp[0] 一定为1,dp[0]是递归中一切数值的基础所在,如果dp[0]是0的话,其他数值都是0了。

这是背包里求排列问题,即:1、2 步 和 2、1 步都是上三个台阶,但是这两种方法不一样!

所以需将target放在外循环,将nums放在内循环。

每一步可以走多次,这是完全背包,内循环需要从前向后遍历。

#include<bits/stdc++.h>
using namespace std;
int main(){
    int n, m;
    cin>>n>>m;
    vector<int> dp(n+5, 0);
    dp[0] = 1;
    for(int i=1; i<=n; i++){ //从1到n可以取n
        for(int j=1; j<=m; j++){ //从1到m可以取m
            if(i-j>=0) dp[i] += dp[i-j]; //i可以等于j
        }
    }
    cout<<dp[n]<<endl;
    return 0;
}
  • 时间复杂度: O(n * m)
  • 空间复杂度: O(n)
  • 14
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值