DAY37|动态规划Part05|完全背包理论基础、LeetCode:518. 零钱兑换 II、377. 组合总和 Ⅳ、70. 爬楼梯 (进阶)

目录

完全背包理论基础

完全背包核心思想

两层遍历顺序

C++代码

[LeetCode:518. 零钱兑换 II](#LeetCode:518. 零钱兑换 II)

基本思路

C++代码

[LeetCode:377. 组合总和 Ⅳ](#LeetCode:377. 组合总和 Ⅳ)

基本思路

C++代码

[卡码网:70. 爬楼梯 (进阶)](#卡码网:70. 爬楼梯 (进阶))

基本思路

C++代码


完全背包理论基础

题目链接

文字讲解:卡码网:52. 携带研究材料

视频讲解:带你学透完全背包问题! 和 01背包有什么差别?遍历顺序上有什么讲究?

完全背包核心思想

有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次) ,求解将哪些物品装入背包里物品价值总和最大。完全背包和01背包问题唯一不同的地方就是,每种物品有无限件

举个例子进行说明:

|-----|----|----|
| | 重量 | 价值 |
| 物品0 | 1 | 15 |
| 物品1 | 3 | 20 |
| 物品2 | 4 | 30 |

其中,背包最大重量为4,并且每件商品都有无限个!

01背包和完全背包唯一不同就是体现在遍历顺序上,我们知道01背包内嵌的循环是从大到小遍历,为了保证每个物品仅被添加一次。而完全背包的物品是可以添加多次的,所以要从小到大去遍历。

// 先遍历物品,再遍历背包
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]);

    }
}

两层遍历顺序

在01背包中其实提到过,01背包中二维dp数组的两个for遍历的先后循序是可以颠倒了,一维dp数组的两个for循环先后循序一定是先遍历物品,再遍历背包容量。而在完全背包中,对于一维dp数组来说,其实两个for循环嵌套顺序是无所谓的!

那么我们到底应该先遍历物品还是先遍历背包呢? 那么就需要看题目要求的到底是组合还是排序相关的问题了。组合不强调元素之间的顺序,排列强调元素之间的顺序!

C++代码

#include <iostream>
#include <vector>
using namespace std;

// 先遍历背包,再遍历物品
void test_CompletePack(vector<int> weight, vector<int> value, int bagWeight) {

    vector<int> dp(bagWeight + 1, 0);

    for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量
        for(int i = 0; i < weight.size(); i++) { // 遍历物品
            if (j - weight[i] >= 0) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
    }
    cout << dp[bagWeight] << endl;
}
int main() {
    int N, V;
    cin >> N >> V;
    vector<int> weight;
    vector<int> value;
    for (int i = 0; i < N; i++) {
        int w;
        int v;
        cin >> w >> v;
        weight.push_back(w);
        value.push_back(v);
    }
    test_CompletePack(weight, value, V);
    return 0;
}

LeetCode:518. 零钱兑换 II

力扣题目链接

文字讲解:LeetCode:518. 零钱兑换 II

视频讲解:动态规划之完全背包,装满背包有多少种方法?组合与排列有讲究!

基本思路

对于这个题目,我们可以看出是求组合而不是求排序,因此不需要考虑元素之前的顺序。

  • 确定dp数组以及下标的含义

dp[j]:将不同面额的硬币放满容量为j的背包中的组合数为dp[j]。

  • 确定递推公式

dp[j] 就是所有的dp[j - coins[i]](考虑coins[i]的情况)相加。所以递推公式:dp[j] += dp[j - coins[i]]; 并且递推公式之前便在讲解01背包中的494.目标和提到过。

  • dp数组如何初始化

首先dp[0]一定要为1,dp[0] = 1是 递归公式的基础。可以理解为不放任何硬币就放满容量为0的背包有1种方法。

  • 确定遍历顺序

本题中我们是外层for循环遍历物品(钱币),内层for遍历背包(金钱总额),还是外层for遍历背包(金钱总额),内层for循环遍历物品(钱币)呢?而本题要求凑成总和的组合数,元素之间明确要求没有顺序。因此应该先遍历物品,在遍历背包。

假设:coins[0] = 1,coins[1] = 5。**如果我们先遍历背包,再遍历物品会出现什么情况呢?**在外层for循环遍历一次的过程中,会遍历多次物品,这样就不仅会出现{1, 5}这种情况,还会出现{5, 1}的情况。

  • 举例推导dp数组

输入: amount = 5, coins = [1, 2, 5] ,dp状态图如下:

最后红色框dp[amount]为最终结果。

C++代码

注意:另外在递推公式中提到dp[j] += dp[j - coins[i]],而为了防止相加的数据超过int数据范围,因此要在递推公式前面加上dp[j] < INT_MAX - dp[j-coins[i]]。

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        vector<int> dp(amount + 1, 0);
        dp[0] = 1; // 只有一种方式达到0
        for (int i = 0; i < coins.size(); i++) { // 遍历物品
            for (int j = coins[i]; j <= amount; j++) { // 遍历背包
                if (dp[j] < INT_MAX - dp[j - coins[i]]) { //防止相加数据超int
                    dp[j] += dp[j - coins[i]];
                }
            }
        }
        return dp[amount]; // 返回组合数
    }
};

LeetCode:377. 组合总和 Ⅳ

力扣题目链接

文字讲解:LeetCode:377. 组合总和 Ⅳ

视频讲解:动态规划之完全背包,装满背包有几种方法?求排列数?

基本思路

根据例一种的提示,显然存在由于元素排列不同而导致结果不同的情况,因此这属于排列问题,需要考虑元素排列的顺序。

  • 确定dp数组以及下标的含义

dp[i]: 从整数数组nums中选择任意整数(可以多次选择同一整数),使其相加和等于target的组个个数为dp[i]个。

  • 确定递推公式

dp[i](考虑nums[j])可以由 dp[i - nums[j]](不考虑nums[j])推导出来。因为只要得到nums[j],排列个数dp[i - nums[j]],就是dp[i]的一部分。

和前面提到的几个题目类似,求装满背包有几种方法,递推公式一般都是dp[i] += dp[i - nums[j]];

  • dp数组如何初始化

dp[0]要初始化为1,这样递归其他dp[i]的时候才会有数值基础。因为题目提到target是正整数,因此实际上dp[0]是没有意义的,仅仅是为了后续递推公式的推导。

  • 确定遍历顺序

个数可以不限使用,说明这是一个完全背包。而得到的集合是排列,说明需要考虑元素之间的顺序。因此我们需要先遍历背包容量,再遍历物品。即外层for循环遍历target,内层for循环遍历nums。

  • 举例来推导dp数组

推导出的dp状态图如下:

C++代码

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] >= 0 && dp[i] < INT_MAX - dp[i - nums[j]]) {
                    dp[i] += dp[i - nums[j]];
                }
            }
        }
        return dp[target];
    }
};

卡码网:70. 爬楼梯 (进阶)

力扣题目链接

文字讲解:卡码网:70. 爬楼梯 (进阶)

视频讲解:动态规划之完全背包,装满背包有几种方法?求排列数?

基本思路

看到这个题目和容易想到前面01背包时候做过的爬楼梯的题目。而之前做的题目最多只能爬2个台阶,但是如果能够最多爬m个台阶呢?其实这个问题同样可以映射为完全背包的问题,即将允许一次最多爬m个台阶看做物品,而台阶总数看作是背包容量。(这里就不在用动规五部曲进行解释了,其实和前面的完全背包是类似的)。

另外需要明确的一点是,爬台阶可以看作是排列问题而并非组合问题,这一点很重要!

C++代码

#include <iostream>
#include <vector>
using namespace std;
int main() {
    int n, m;
    while (cin >> n >> m) {
        vector<int> dp(n + 1, 0);
        dp[0] = 1;
        for (int i = 1; i <= n; i++) { // 遍历背包
            for (int j = 1; j <= m; j++) { // 遍历物品
                if (i - j >= 0) dp[i] += dp[i - j];
            }
        }
        cout << dp[n] << endl;
    }
}
相关推荐
pianmian11 小时前
贪心算法.
算法·贪心算法
m0_694938012 小时前
Leetcode打卡:字符串及其反转中是否存在同一子字符串
linux·服务器·leetcode
chenziang12 小时前
leetcode hot 100 二叉搜索
数据结构·算法·leetcode
single5943 小时前
【c++笔试强训】(第四十五篇)
java·开发语言·数据结构·c++·算法
呆头鹅AI工作室4 小时前
基于特征工程(pca分析)、小波去噪以及数据增强,同时采用基于注意力机制的BiLSTM、随机森林、ARIMA模型进行序列数据预测
人工智能·深度学习·神经网络·算法·随机森林·回归
一勺汤5 小时前
YOLO11改进-注意力-引入自调制特征聚合模块SMFA
人工智能·深度学习·算法·yolo·目标检测·计算机视觉·目标跟踪
每天写点bug5 小时前
【golang】map遍历注意事项
开发语言·算法·golang
程序员JerrySUN5 小时前
BitBake 执行流程深度解析:从理论到实践
linux·开发语言·嵌入式硬件·算法·架构
王老师青少年编程6 小时前
gesp(二级)(16)洛谷:B4037:[GESP202409 二级] 小杨的 N 字矩阵
数据结构·c++·算法·gesp·csp·信奥赛
robin_suli6 小时前
动态规划子序列问题系列一>等差序列划分II
算法·动态规划