代码随想录算法训练营DAY44|C++动态规划Part6|完全背包理论基础、518.零钱兑换II、377. 组合总和 Ⅳ

文章目录

  • 完全背包理论基础
    • 完全背包问题的定义
    • 与01背包的核心区别
    • 为什么完全背包的循环顺序可以互换?
    • CPP代码
  • ⭐️518.零钱兑换II
    • 思路
    • CPP代码
  • ⭐️377. 组合总和 Ⅳ
    • 思路
    • CPP代码
  • 扩展题

完全背包理论基础

卡码网第52题

文章链接:完全背包理论基础

视频链接:带你学透完全背包问题!

完全背包问题的定义

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

完全背包和01背包问题唯一不同的地方就是,每种物品有无限件

重量价值
物品0115
物品1320
物品2430

每件商品有无限个,问背包能背的物品的最大价值是多少?

与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]);
  }
}

01背包的内循环是从大到小遍历的,目的就是为了保证每个物品只被添加一次;

那么对于完全背包问题的物品可以多次添加,所以要从小到大去遍历,这样的遍历方式使得每个物品可以在更新当前容量 j 的时候重复利用之前已经计算过的结果(也就是说在同一个 i 循环中,dp[j] 可以从 dp[j - weight[i]] 中获得更新,而 dp[j - weight[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]);
  }
}

为什么完全背包的循环顺序可以互换?

在0-1背包理论基础(一)、0-1背包理论基础之滚动数组(二)文章中已经指出在01背包问题中,一维dp数组的两个for循环一定是先遍历物品,再遍历背包容量。

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

1. 先遍历物品,再遍历背包容量

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

我们可以看出,每次考虑一个物品,然后更新所有可能的背包容量。由于是正序更新,所以 dp[j] 可以反复从 dp[j - weight[i]] 获取价值,实现了物品的重复选择。状态图展示如下:

2. 先遍历背包容量,再遍历物品

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

在这种情况下,每个背包容量都尝试添加所有可能的物品。这样的循环同样可以正常工作,因为每个 dp[j] 都会考虑是否加入每个物品 i,并且仍然可以通过 dp[j - weight[i]] 反复获得价值,从而实现物品的重复选择。

CPP代码

这里是卡码网第52题问题的答案。

#include <bits/stdc++.h>
using namespace std;

int numsMaterials;
int bagWeight;

void solve () {
    vector<int> weights(numsMaterials, 0);
    vector<int> values(numsMaterials, 0);
    int weight, value;
    
    for (int i = 0; i < numsMaterials; i++) {
        int weight, value;
        cin >> weight >> value;
        weights[i] = weight;
        values[i] = value;
    }
    
    
    vector<int> dp(bagWeight + 1, 0);
    
    for (int i = 0; i < numsMaterials; i++) {
        for (int j = weights[i]; j<= bagWeight; j++) {
            dp[j] = max(dp[j], dp[j - weights[i]] + values[i]);
        }
    }
    
    cout << dp[bagWeight] <<endl;
}


int main () {
    cin >> numsMaterials >> bagWeight;
    solve();
    
    
    return 0;
}

⭐️518.零钱兑换II

力扣题目链接

文章链接:518.零钱兑换II

视频链接:装满背包有多少种方法?组合与排列有讲究!| LeetCode:518.零钱兑换II

状态:「错误点」
1. 关于dp[0]应该初始化为1,因为系统后台默认的是dp[0]应该等于1
2. 第一个遍历物品的时候肯定从零开始啊!不知道为什么第一次写的时候从1开始了。

思路

首先可以确定bagWeight=amount=5,再一个weight=value=coins。再一个本题和纯完全背包问题还不一样,纯完全背包是凑成背包最大价值是多少,而本题是要求凑成总金额的物品组合个数!

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

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

  • 确定递推公式

dp[j]就是所有的dp[j-coins[i]]情况相加。我们已经在这篇文章中讨论过该类问题:494.目标和

  • dp数组如何初始化

卡哥文章里写了,后台测试数据是默认,amount = 0 的情况,组合数为1的

也就是说dp[0]=1——凑成总金额0的货币组合数为1。

下标非0的dp[j]初始化为0,这样累计加dp[j - coins[i]]的时候才不会影响真正的dp[j]

  • 确定遍历顺序

对于一个纯背包问题来说,遍历顺序并不重要,因为他是一个排列问题.

但是本题中是一个明显的组合问题,比如说我们可以有一种分配方法是{1, 5}但是绝对不能再有{5, 1}。所以对于遍历顺序而言一定是 外层for循环遍历物品(钱币),内层for遍历背包(金钱总额)的情况

for (int i = 0; i < coins.size(); i++) { // 遍历物品
    for (int j = coins[i]; j <= amount; j++) { // 遍历背包容量
        dp[j] += dp[j - coins[i]];
    }
}
  • 举例推导dp数组

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

CPP代码

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]];
            }
        }
        return dp[amount];
    }
};

⭐️377. 组合总和 Ⅳ

力扣题目链接

文章链接:377. 组合总和 Ⅳ

视频链接:装满背包有几种方法?求排列数?| LeetCode:377.组合总和IV

状态:典型的排列问题,其实就是一个区别,就是遍历顺序的问题,只要我们先遍历背包,再遍历物品,就可以把物品进行反复选择,从而得出排列总和为target的个数

首先先明确一下什么是排列,什么是组合:

  • 组合不强调顺序,(1,5)和(5,1)是同一个组合。

  • 排列强调顺序,(1,5)和(5,1)是两个不同的排列。

我们在写回溯的时候,写过几次组合总和的问题,里面其实本质也是求排列,不过回溯是要求把所有的排列都列出来,而不是求排列总和相等的个数。

如果本题要把排列都列出来的话,只能使用回溯算法爆搜

思路

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

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

  • 确定递推公式

dp[j](考虑nums[j])可以由 dp[i - nums[j]](不考虑nums[j]) 推导出来。

因为只要得到nums[j],排列个数dp[j - nums[i]],就是dp[j]的一部分。

本题还是我们经常谈论的,求装背包有几种方法,递推公式一般都是dp[j] += dp[j - nums[i]]

  • dp数组如何初始化(dp的初始化非常重要)

在求装满背包的多少种组合问题时,其实就是让dp[0]初始化为1,这样递归其他dp[i]的时候才会有数值基础

然后非零下标初始化为0

  • 确定遍历顺序

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

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

如果把遍历nums(物品)放在外循环,遍历target的作为内循环的话,举一个例子:计算dp[4]的时候,结果集只有 {1,3} 这样的集合,不会有{3,1}这样的集合,因为nums遍历放在外层,3只能出现在1后面!

  • 举例来推导dp数组(当题目不能AC的时候一定要进行尝试)

20230310000625

CPP代码

关于递推公式前的条件判断语句:
一方面是防止下标超过索引下标;
另一方面防止整数溢出。

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];
    }
};
//直接讲元素定义成题目规定的uint
class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<uint> dp(target + 1, 0);
        dp[0] = 1;

        for (int j = 0; j <= target; j++) {
            for (int i = 0; i < nums.size(); i++) {
                if (j >= nums[i])
                    dp[j] += dp[j - nums[i]]; 
            }
        }
        return dp[target];
    }
};

扩展题

还记得我们的爬楼梯吗?

70.爬楼梯

爬楼梯中,如果需要n阶才能爬到楼顶,每次你可以爬 1 或 2 个台阶。有多少种不同的方法可以爬到楼顶呢?

进一步:

如果一次可以爬3、4甚至m个台阶,一共需要爬n阶才能爬到楼顶,又如何求爬到楼顶的方法数呢?

联系到本题来看,

一步可以爬几个台阶,就相当于本题的nums=[1, 2, 3],就是一步可以爬1、2、3个台阶,target就相当于是要target阶才能爬到楼顶。

也就是装满这个背包(爬到楼顶)有多少种方法,典型的排列问题,和本题是一样一样的。

明天我们就是爬楼梯(进阶版)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/599256.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++】C++11--- 类的新功能

目录 类的新功能 默认成员函数 示例 类成员变量初始化 强制生成默认函数的关键字default 禁止生成默认函数的关键字delete 类的新功能 默认成员函数 构造函数析构函数拷贝构造函数拷贝赋值重载取地址重载const取地址重载 C11在原先的6个默认成员函数的基础上&#xff0c…

PHP基于vscode医院安全不良事件管理系统源码(AEMS)前端vue2+element+后端laravel8不良事件上报与闭环管理

PHP基于vscode医院安全不良事件管理系统源码&#xff08;AEMS&#xff09;前端vue2element后端laravel8不良事件上报与闭环管理 医院不良事件上报与管理系统结合现代医院管理思路&#xff0c;遵照PDCA全面质量循环管理方法而设计&#xff0c;并在多家大型三甲医院成熟运用。系统…

第29章-SR技术概述

1. SR技术的产生背景 2. SR技术的基本概念 3. SR技术的基本原理 1. SR技术的产生背景 1.1 传统的路由器设备因其转发性能较低 ① 最长匹配算法的缺点&#xff0c;需要遍历整个路由表&#xff1b; ② 早期路由器多采用通用CPU进行转发处理&#xff0c;性能有限&#xff1b; ③…

word:三线表的绘制【攻略】

word&#xff1a;三线表的绘制【攻略】 前言版权推荐word&#xff1a;三线表的绘制效果简单方法另外的方法 最后 前言 2024-5-7 18:25:08 以下内容源自《【攻略】》 仅供学习交流使用 版权 禁止其他平台发布时删除以下此话 本文首次发布于CSDN平台 作者是CSDN日星月云 博客…

Linux--基础IO(文件描述符fd)

目录 1.回顾一下文件 2.理解文件 下面就是系统调用的文件操作 文件描述符fd&#xff0c;fd的本质是什么&#xff1f; 读写文件与内核级缓存区的关系 据上理论我们就可以知道&#xff1a;open在干什么 3.理解Linux一切皆文件 4.C语言中的FILE* 1.回顾一下文件 先来段代码…

Upload-labs 靶场通关解析(上)

前言 文件上传漏洞是一种常见的网络安全漏洞&#xff0c;存在于许多Web应用程序中。攻击者利用这个漏洞可以上传恶意文件到目标服务器&#xff0c;从而执行各种恶意操作&#xff0c;如执行恶意代码、获取敏感信息、控制服务器等。 文件上传漏洞的原理是&#xff0c;Web应用程…

(✌)粤嵌—2024/5/7—除自身以外数组的乘积

代码实现&#xff1a; /*** Note: The returned array must be malloced, assume caller calls free().*/ int* productExceptSelf(int *nums, int numsSize, int *returnSize) {// 左乘积int l[numsSize];l[0] 1;for (int i 1; i < numsSize; i) {l[i] l[i - 1] * nums[…

PyQt6--Python桌面开发(1.安装配置环境)

一.PyQt6简介 PyQt&#xff1a;PyQt是一个功能强大且成熟的GUI框架&#xff0c;基于Qt库。它提供了丰富的组件、布局和主题选项&#xff0c;以及强大的功能和灵活性。PyQt的优点是它具有现代化的外观和丰富的功能&#xff0c;适用于复杂的GUI应用程序。然而&#xff0c;由于Py…

VMware导入ova/ovf虚拟机文件

1.文件-打开-ova文件 2.为新虚拟机起名称 3.等待导入 4.导入完成&#xff0c;可以开始使用 参考链接&#xff1a;VMware导入ova/ovf虚拟机文件

数仓分层——ODS、DW、ADS

一、什么是数仓分层 数据仓库分层是一种组织和管理数据仓库的结构化方法&#xff0c;它将数据仓库划分为不同的层次或级别&#xff0c;每个层次具有特定的功能和目的。这种分层方法有助于管理数据仓库中的数据流程、数据处理和数据访问&#xff0c;并提供一种清晰的结构来支持…

ArrayList线程安全问题解决方案

jdk8 Stream API的出现大大简化了我们对于集合元素的处理代码&#xff0c;对于串行流来说&#xff0c;无需考虑线程安全问题&#xff1b;但是&#xff0c;对于并行流来说&#xff0c;由于它是以多线程的方式并行处理同一个集合中的数据元素的&#xff0c;因此&#xff0c;存在着…

[Android]国内流行的应用市场

国内应用市场的特点 由于Google Play商店服务国内不可用&#xff0c;有许多其它的应用商店充当Android应用的分发渠道。这些商店通常由中国的主要科技公司运营&#xff0c;每个商店都有自己的运营策略和用户基础。 全球移动供应商市场份额&#xff1a;https://gs.statcounter…

我独自升级崛起PC下载安装教程 我独自升级崛起PC下载教程

《我独自升级&#xff1a;崛起》这款游戏灵感源自热门网络漫画《我独自升级》&#xff0c;是一款深度浸入式RPG游戏。它不仅呈献给玩家一个情节错综复杂、引人入胜的故事线&#xff0c;让玩家能紧随主角步伐&#xff0c;亲历其成长的点点滴滴&#xff0c;还自豪地展示了琳琅满目…

geojson文件规格

geojson文件示例&#xff0c; {"type": "FeatureCollection","features": [{"type": "Feature","geometry": {"type": "Point","coordinates": [102.0, 0.5]},"properties&q…

RT-DETR-20240507周更说明|更新Inner-IoU、Focal-IoU、Focaler-IoU等数十种IoU计算方式

RT-DETR改进专栏|包含主干、模块、注意力、损失函数等改进 专栏介绍 本专栏包含模块、卷积、检测头、损失等深度学习前沿改进,目前已有改进点70&#xff01;每周更新。 20240507更新说明&#xff1a; ⭐⭐ 更新CIoU、DIoU、MDPIoU、GIoU、EIoU、SIoU、ShapeIou、PowerfulIoU、…

聚乙烯醇(PVA)纳米纤维膜的性能介绍

聚乙烯醇&#xff08;PVA&#xff09;纳米纤维膜是一种由聚乙烯醇&#xff08;PVA&#xff09;分子组成的纳米级薄膜。这种材料具有许多优良的性能和应用前景&#xff0c;具体如下&#xff1a; 制备工艺&#xff1a;聚乙烯醇纳米纤维膜可以通过静电纺丝等方法制备。具体步骤包括…

网络知识点之—QoS

QoS&#xff08;Quality of Service&#xff0c;服务质量&#xff09;指一个网络能够利用各种基础技术&#xff0c;为指定的网络通信提供更好的服务能力&#xff0c;是网络的一种安全机制&#xff0c; 是用来解决网络延迟和阻塞等问题的一种技术。QoS的保证对于容量有限的网络来…

5000A信号发生器使用方法

背景 gnss工作需要使用的5000A&#xff0c;所以做成文档&#xff0c;用于其他员工学习。 下载星历数据 https://cddis.nasa.gov/archive/gnss/data/daily/2024/brdc/ 修改daily中的年份&#xff0c;就可以获取相关截至时间的星历数据 brcd数据格式 第一行记录了卫星的PRN号&a…

科创板门槛升级!解析中国量子企业的上市之路与国际比拼

4月30日晚&#xff0c;中国证监会于发布了修订后的《科创属性评价指引&#xff08;试行&#xff09;》&#xff08;以下简称“新指引”&#xff09;&#xff0c;该指引自发布日起正式生效。本次修订对原有指引中的部分标准进行了调整&#xff0c;具体如下&#xff1a; 1&#x…

远程桌面连接不上,远程桌面连接不上的专业解决策略

在信息技术领域&#xff0c;远程桌面连接是一种非常重要的工具&#xff0c;它允许用户从任何地点、任何时间访问和操作远程计算机。然而&#xff0c;当远程桌面连接出现问题时&#xff0c;可能会严重影响工作效率。以下是一些可能导致远程桌面连接不上的原因以及相应的解决方案…