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

news2024/11/24 15:33:22

文章目录

  • 完全背包理论基础
    • 完全背包问题的定义
    • 与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阶才能爬到楼顶。

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

android系统serviceManger源码解析

一&#xff0c;serviceManger时序图 本文涉及到的源码文件&#xff1a; /frameworks/native/cmds/servicemanager/main.cpp /frameworks/native/libs/binder/ProcessState.cpp /frameworks/native/cmds/servicemanager/ServiceManager.cpp /frameworks/native/libs/binder/IP…

cURL:命令行下的网络工具

序言 在当今互联网时代&#xff0c;我们经常需要与远程服务器通信&#xff0c;获取数据、发送请求或下载文件。在这些情况下&#xff0c;cURL 是一个强大而灵活的工具&#xff0c;它允许我们通过命令行进行各种类型的网络交互。本文将深入探讨 cURL 的基本用法以及一些高级功能…

融资融券利率最低多少:一文了解2024年最低融资融券开通攻略(利率4%-5%)

一、什么是融资融券利率&#xff1f; 融资融券利率通常指的是投资者在进行融资融券交易时需要支付给券商的利息费用的比率&#xff08;年化利率&#xff09;。 具体来说&#xff0c;融资融券利率包括两部分&#xff1a; 1、融资利率&#xff1a;这是客户借入资金进行证券买入…

S32DS查看freeRTOS运行状态

在工具栏上面可以选择查看任务、队列、计时器、堆栈&#xff0c;都需要暂停下来查看。 打开之后千万不要急&#xff0c;因为需要比较久的时间&#xff0c;一个一个字节地读取出来。 任务列表是最常用的&#xff0c;任务名称、句柄、状态、优先级和堆栈使用情况都能看到。 计时…

智能网联汽车网络和数据安全态势分析

文章目录 前言一、我国智能网联汽车安全态势分析(一)我国高度重视智能网联汽车安全发展(二)产业高速发展伴随网络安全隐患(三)网络和数据安全风险事件威胁加剧二、智能网联汽车网络和数据安全典型实践剖析(一)立标准规范,把牢安全发展“方向盘”(二)强车主服务,系好…

企业计算机服务器中了locked勒索病毒怎么处理,locked勒索病毒解密建议

随着互联网技术在企业当中的应用&#xff0c;越来越多的企业利用网络开展各项工作业务&#xff0c;网络为企业提供了极大便利&#xff0c;也大大加快了企业发展步伐&#xff0c;提高了企业生产办公效率。但网络技术的发展也为企业的数据安全带来严重威胁。近期&#xff0c;云天…

【GameFi】链游 | Seraph | 区块链上的动作角色扮演 NFT 装备收集和掠夺游戏

官网下载 新赛季公告&#xff1a;https://www.seraph.game/#/news/357 开始时间&#xff1a;2024年4月19日 11:00 (UTC8&#xff09; discard会有人发送一些激活码&#xff0c;或者有一些活动&#xff0c;只需要填表格关注账号&#xff0c;参与了就会将激活码发到你的邮箱 …

STM32单片机实战开发笔记-独立看门狗IWDG

嵌入式单片机开发实战例程合集&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/11av8rV45dtHO0EHf8e_Q0Q?pwd28ab 提取码&#xff1a;28ab IWDG模块测试 1、功能描述 STM32F10X内置两个看门狗&#xff0c;提供了更高的安全性&#xff0c;时间的精确下性和使用的灵活性…

OpenHarmony 实战开发——ABI

OpenHarmony系统支持丰富的设备形态&#xff0c;支持多种架构指令集&#xff0c;支持多种操作系统内核&#xff1b;为了应用在各种OpenHarmony设备上的兼容性&#xff0c;本文定义了"OHOS" ABI&#xff08;Application Binary Interface&#xff09;的基础标准&#…

数据库大作业——基于qt开发的图书管理系统 (一)环境的配置与项目需求的分析

前言 博主最近数据库原理结课要做课程设计了,要求开发基于数据库实现的图书管理系统&#xff0c;博主想了想决定做一个基于Qt的图书管理系统,博主在此之前其实也没有用过多少Qt&#xff0c;仅以此专栏记录博主学习与开发的全过程&#xff0c;大家一起学习&#xff0c;一起进步…

Transformer - 编码器和解码器中的QKV分别来自哪

Transformer - 编码器和解码器中的QKV分别来自哪 flyfish Transformer - 注意⼒机制 Scaled Dot-Product Attention 计算过程 Transformer - 注意⼒机制 代码实现 Transformer - 注意⼒机制 Scaled Dot-Product Attention不同的代码比较 Transformer - 注意⼒机制 代码解释 Tr…

【Pytorch】2.TensorBoard的运用

什么是TensorBoard 是一个可视化和理解深度爵溪模型的工具。它可以通过显示模型结构、训练过程中的指标和图形化展示训练的效果来帮助用户更好地理解和调试他们的模型 TensorBoard的使用 安装tensorboard环境 在终端使用 conda install tensorboard通过anaconda安装 导入类Sum…

大厂Java面试题:MyBatis是如何进行分页的?分页插件的实现原理是什么?

大家好&#xff0c;我是王有志。 今天给大家带来的是一道来自京东的关于 MyBatis 实现分页功能的面试题&#xff1a;MyBatis是如何进行分页的&#xff1f;分页插件的实现原理是什么&#xff1f;通常&#xff0c;分页的方式可以分为两种&#xff1a; 逻辑&#xff08;内存&…

c#绘制渐变色的Led

项目场景&#xff1a; c#绘制渐变色的button using System; using System.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; using System.Windows.Forms; using static System.Windows.Forms.AxHost;namespace WindowsFormsApp2 {public class Gradie…

基于FPGA的数字信号处理(9)--定点数据的两种溢出处理模式:饱和(Saturate)和绕回(Wrap)

1、前言 在逻辑设计中&#xff0c;为了保证运算结果的正确性&#xff0c;常常需要对结果的位宽进行扩展。比如2个3bits的无符号数相加&#xff0c;只有将结果设定为4bits&#xff0c;才能保证结果一定是正确的。不然&#xff0c;某些情况如77 14(1110)&#xff0c;如果结果只…

FileBird Pro插件下载:革新您的WordPress媒体库管理

WordPress媒体库是您网站的重要组成部分&#xff0c;它存储了所有的图片、视频、文档等文件。但随着网站的扩展&#xff0c;媒体库的管理变得越来越复杂。FileBird Pro插件&#xff0c;作为一款专为WordPress用户设计的媒体库管理工具&#xff0c;以其直观的界面和强大的功能&a…

嵌入式系统应用-拓展-FLASH之操作 SFUD (Serial Flash Universal Driver)之KEIL移植

1 SFUD介绍 1.1 初步介绍 SFUD 是一个开源的串行 SPI 闪存通用驱动库。由于市面上有各种类型的串行闪存设备&#xff0c;每种设备都具有不同的规格和指令&#xff0c;因此 SFUD 的设计目的是解决这些差异。这使得我们的产品可以支持不同品牌和规格的闪存&#xff0c;增强了软…

幻兽帕鲁游戏主机多少钱?幻兽帕鲁游戏服务器一个月仅需32元

随着游戏产业的蓬勃发展&#xff0c;腾讯云紧跟潮流&#xff0c;推出了针对热门游戏《幻兽帕鲁Palworld》的专属游戏服务器。对于广大游戏爱好者来说&#xff0c;这无疑是一个激动人心的消息。那么&#xff0c;腾讯云幻兽帕鲁游戏主机到底多少钱呢&#xff1f;让我们一起来揭晓…

施耐德EOCR3BZ2-WRAUH数显型马达保护器

施耐德EOCR3BZ2-WRAUH数显型马达保护器 ◆ 高性能多功能电动机保护器 ◆ 实时监测/高精度 ◆ 可为单相或三相交流负载提供可靠保护 ◆ 保护功能&#xff1a;过电流、欠电流、反转、缺相、失速、电流不平衡和接地保护功能 ◆ 附加功能&#xff1a;掉电保护、运行时间累计、…

太阳能4G/5G无线视频监控系统解决方案(下)

目录 一、系统简介 &#xff08;一&#xff09;系统概述 &#xff08;二&#xff09;系统特点 &#xff08;三&#xff09;应用场景 二、系统原理和架构 &#xff08;一&#xff09;系统原理 1、拓扑图 2、系统组成介绍 &#xff08;二&#xff09;太阳能供电子系统 …