代码随想录第四十四天 | 动态规划 完全背包:纯完全背包理论基础(卡码网第52题);应用(注意遍历顺序):组合(518),排列(377)

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

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

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

leetcode上没有纯完全背包问题,都是需要完全背包的各种应用,需要转化成完全背包问题,这里还是以纯完全背包问题进行讲解理论和原理

依然举这个例子:
背包最大重量为4
物品为:

重量价值
物品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背包内嵌的循环从大到小遍历为了保证每个物品仅被添加一次
完全背包的物品是可以添加多次的,所以要从小到大去遍历,即:

// 先遍历物品,再遍历背包
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状态图如下:
完全背包dp状态图
其实还有一个很重要的问题,为什么遍历物品外层循环遍历背包容量内层循环
01背包二维dp数组两个for遍历的先后循序可以颠倒了,一维dp数组两个for循环先后循序一定是先遍历物品,再遍历背包容量,之所以会有两个循环的固定顺序,是为了内层循环从大到小可以去重

完全背包中,对于一维dp数组来说,其实两个for循环嵌套顺序无所谓的,因为完全背包不需要去重
dp[j]根据 下标j之前所对应的dp[j]计算出来的。 只要保证下标j之前的dp[j]都是经过计算的就可以了

遍历物品在外层循环,遍历背包容量在内层循环,状态如图:
遍历物品在外层循环,遍历背包容量在内层循环 的 完全背包dp状态图
遍历背包容量在外层循环,遍历物品在内层循环,状态如图:
遍历背包容量在外层循环,遍历物品在内层循环 的 完全背包dp状态图
先遍历背包再遍历物品,代码如下:

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

使用 卡码网第52题 进行测试
根据思路实现代码:

注意开的动态规划数组大小为v + 1,0-v重量

#include <bits/stdc++.h>
using namespace std;void getMaxValue(int n, int v) {vector<int> dp(v + 1, 0);//注意开的动态规划数组大小为v+1,0-v重量vector<int> weight(n, 0);vector<int> value(n, 0);for(int i = 0; i < n; i++) {cin >> weight[i] >> value[i];}for(int i = 0; i < value.size(); i++) {for(int j = weight[i]; j <= v; j++) {dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);}}cout << dp[v] << endl;return;
}int main() {int n;int v;while(cin >> n >> v) {getMaxValue(n, v);}return 0;
}

代码随想录实现代码:

#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;
}

1.2 总结

全文说的都是对于 “纯” 完全背包问题,其for循环的先后顺序可以颠倒
但如果题目稍稍有点变化,就会体现在遍历顺序
如果问装满背包有几种方式的话? 那么两个for循环的先后顺序就有很大区别

2、完全背包 组合

2.1 leetcode 518:零钱兑换II

第一遍代码:
dp[0] = 1; 需要有这个初值正好 j 就是货币的面额的时候就是1
注意两个循环的顺序物品在外价值在内

class Solution {
public:int change(int amount, vector<int>& coins) {vector<int> dp(amount + 1, 0);dp[0] = 1;//需要有这个初值,正好amount就是货币的面额的时候就是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];}
};

思路
这是一道典型的背包问题,一看到钱币数量不限,就知道这是一个完全背包
但本题和纯完全背包不一样纯完全背包凑成背包最大价值是多少,而本题是要求凑成总金额的物品组合个数

注意题目描述中是凑成总金额的硬币组合数,为什么强调是组合数呢(第一遍代码没考虑到)

例如示例一:
5 = 2 + 2 + 1
5 = 2 + 1 + 2
这是一种组合,都是 2 2 1
如果问的是排列数,那么上面就是两种排列

组合 不强调元素之间的顺序排列 强调元素之间的顺序

回归本题,动规五步曲来分析如下:
1、确定dp数组以及下标的含义
dp[j]凑成总金额j货币组合数为dp[j]

2、确定递推公式
dp[j] 就是所有的dp[j - coins[i]](考虑coins[i]的情况)相加
所以递推公式:dp[j] += dp[j - coins[i]];

这个递推公式大家应该不陌生了,在讲解01背包题目的时候 leetcode 494:目标和 中就讲解了,求装满背包有几种方法公式都是:dp[j] += dp[j - nums[i]];

3、dp数组如何初始化
首先dp[0]一定要为1,dp[0] = 1是 递归公式的基础。如果dp[0] = 0 的话,后面所有推导出来的值都是0了
下标非0的dp[j] 初始化为0,这样累计加dp[j - coins[i]]的时候才不会影响真正的dp[j]

dp[0]=1说明了一种情况:如果正好选了coins[i]后,也就是j-coins[i] == 0的情况表示这个硬币刚好能选,此时dp[0]为1表示只选coins[i]存在这样的一种选法

4、确定遍历顺序
本题中我们是外层for循环遍历物品(钱币),内层for遍历背包(金钱总额),还是外层for遍历背包(金钱总额),内层for循环遍历物品(钱币)呢

因为纯完全背包求得装满背包的最大价值是多少,和凑成总和的元素有没有顺序 没关系,即:有顺序也行,没有顺序也行
本题要求凑成总和的组合数,元素之间明确要求没有顺序

所以纯完全背包是能凑成总和就行,不用管怎么凑的;本题是求凑出来的方案个数,且每个方案个数是为组合数,那么本题,两个for循环的先后顺序可就有说法

我们先来看 外层for循环遍历物品(钱币),内层for遍历背包(金钱总额) 的情况。
代码如下:

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

假设:coins[0] = 1,coins[1] = 5

那么就是先把1加入计算,然后再把5加入计算,得到的方法数量**只有{1, 5}这种情况。而不会出现{5, 1}**的情况

原因是只有dp[1]参与了第二次i = 1的时候得到dp[6],因为第二层开始的起点是从coins[i] 开始,意味着当把 coins[1] 5加入计算后,不会再加上dp[1] 的值了(所以不存在{5,1}只有{1,5}(原来存在1,补上个5就成了,统计的其实是1的组合次数)),而是直接利用之前的dp[1] 整出了dp[6]
所以这种遍历顺序中dp[j]里计算的是组合数

如果把两个for交换顺序,代码如下:

for (int j = 0; j <= amount; j++) { // 遍历背包容量for (int i = 0; i < coins.size(); i++) { // 遍历物品if (j - coins[i] >= 0) dp[j] += dp[j - coins[i]];}
}

背包容量的每一个值,都是经过 1 和 5 的计算,包含了 {1, 5} 和 {5, 1} 两种情况

原因是除了dp[1]参与了第二次i = 1的时候得到dp[6]dp[5]也参与了,因为第二层都是遍历所有物品的,意味着当把 coins[1] 5加入计算后,还要再加上dp[5] 的值(所以比之前多加了{5,1}),同时利用之前的dp[1] 和之前的dp[5] 整出了dp[6]
此时dp[j]里算出来的就是排列数

5、举例推导dp数组
输入: amount = 5, coins = [1, 2, 5] ,dp状态图如下:
举例推导dp数组
代码随想录实现代码与第一遍代码一致
时间复杂度: O(mn),其中 m 是amount,n 是 coins 的长度
空间复杂度: O(m)

2.2 leetcode 518:总结

本题的递推公式,其实我们在 leetcode 494:目标和 中就已经讲过了,而难点在于遍历顺序
求装满背包有几种方案的时候,认清遍历顺序非常关键

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

3、完全背包 排列

3.1 leetcode 377:组合总和 Ⅳ(求的是排列)

第一遍代码:

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

报错:runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'
说明超int限制了,首先检查有数相加的情况,看看有没有超过
if加一条限制,保证相加不超过int的取值范围dp[i] < INT_MAX - dp[i - nums[j]]

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]];//要保证相加不超过int的取值范围}}return dp[target];}
};

代码随想录思路
本题其实是求排列
本题求的是排列总和,而且仅仅是求排列总和的个数,并不是把所有的排列都列出来
如果本题要把排列都列出来的话,只能使用回溯算法爆搜

动规五部曲分析如下:
1、确定dp数组以及下标的含义
dp[i]: 凑成目标正整数为i排列个数为dp[i]

2、确定递推公式
dp[i]考虑nums[j])可以由 dp[i - nums[j]]不考虑nums[j] (理解为什么上节是组合 这节是排列的核心)推导出来,因为只要得到nums[j]排列个数dp[i - nums[j]],就是dp[i]的一部分

在 leetcode 494:目标和 和 leetcode 518:零钱兑换II 中已经讲过了,求装满背包有几种方法递推公式一般都是dp[i] += dp[i - nums[j]];,本题也一样

3、dp数组如何初始化
因为递推公式dp[i] += dp[i - nums[j]]的缘故,dp[0]初始化为1,这样递归其他dp[i]的时候才会有数值基础
至于dp[0] = 1 有没有意义,因为题目中说了:给定目标值正整数! 所以dp[0] = 1没有意义的,仅仅是为了推导递推公式

至于非0下标的dp[i]应该初始为多少呢
初始化为0,这样才不会影响dp[i]累加所有的dp[i - nums[j]]

4、确定遍历顺序
个数可以不限使用,说明这是一个完全背包
得到的集合是排列,说明需要考虑元素之间的顺序
本题要求的是排列,那么这个for循环嵌套的顺序可以有说法了

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

所以本题遍历顺序最终遍历顺序:target(背包)放在外循环,将nums(物品)放在内循环内循环从前到后遍历

5、举例来推导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];}
};

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

3.2 leetcode 377:总结

求装满背包有几种方法,递归公式都是一样的,没有什么差别,但关键在于遍历顺序

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

lombok依赖介绍(帮助我们消除冗长代码,如get,set方法)

前言 lombok 是一个 Java 工具库&#xff0c;通过注解的方式&#xff0c;简化 Java 开发。要想使用 lombok 中的注解&#xff0c;我们需要先引入依赖&#xff0c;推荐看idea必装插件EditStarters&#xff08;快速引入依赖&#xff09;&#xff0c;lombok是⼀款在编译期⽣成代码…

从公共业务提取来看架构演进——帐号权限篇

1. 引言 在产品业务多元化的过程中&#xff0c;往往会遇到一些与业务发展不匹配的架构问题&#xff0c;这些问题可能会在业务迭代中以性能差、重复开发等形式表现出来&#xff0c;进而给系统的扩展和维护带来一些挑战。 本文准备以一个帐号权限的业务为例&#xff0c;来讨论下…

arcgis pro模型构建器

如果你不想部署代码包环境来写arcpy代码&#xff0c;还想实现批量或便携封装的操作工具&#xff0c;那么使用模型构建器是最好的选择。1.简介模型构建器 1.1双击打开模型构建器 1.2简单模型构建步骤 先梳理整个操作流程&#xff0c;在纸上绘制在工具箱中找到所需工具拖进来把…

使用Anaconda安装TensorFlow环境以及没有搜到的报错的解决方法

1.在官网下载Anaconda 这一步几乎不会有人报错 下稳定的版本 或者最新的版本都可以 2.TensorFlow分两个版本 一个是用cpu跑 另一个是用gpu跑 显而易见 cpu的计算性能已经比不上现在主流的显卡了 所以有独显的电脑尽量安装gpu版本 CPU版本: 先给出cpu版本的安装方法: 打开A…

maven项目子类项目版本与父类版本不一致

项目的依赖关系 A项目的父pom是spring boot&#xff0c;A依赖pom B&#xff0c;B依赖hibernate B引用的hibernate版本为8.0.1 A引用的hibernate版本为6.2.0 maven helper插件显示无依赖冲突 这就很奇怪&#xff0c;为何依赖版本有问题呢&#xff1f;是在看不出来问题&#xff…

mysql数据库的备份和恢复

目录 一、备份和恢复 1、备份&#xff1a; 2、备份的方法&#xff1a; 2.1物理备份&#xff1a; 2.2、逻辑备份 2.3增量备份&#xff1a; 一、备份和恢复 1、备份&#xff1a; 先备份再恢复 备份&#xff1a;完全备份&#xff0c;增量备份 完全备份&#xff1a;将整个…

hadoop配置

服务规划 gz上传文件&#xff0c;解压文件&#xff0c;创建软连接 cd etc 修改workers文件 配置hadoop-env.sh&#xff0c;这个文件作用主要是Hadoop运行的环境变量 export JAVA_HOME/export/server/jdk export HADOOP_HOME/export/server/hadoop export HADOOP_CONF_DIR$HADOO…

容器核心技术-Cgroups

一、Cgroups Cgroups &#xff08;Control Groups&#xff09; 是 Linux 下用于对一个或一组进程进行资源控制和监控的机制&#xff1b;可以对诸如CPU使用时间、内存、磁盘I&#xff0f;O等进程所需的资源进行限制&#xff1b;不同资源的具体管理工作由相应的Cgroup 子系统&am…

LabVIEW开发多速率实时混合仿真

LabVIEW开发多速率实时混合仿真 混合仿真是一种子结构技术&#xff0c;通过将数值建模的优点与实验测试的优点相结合来模拟感兴趣的结构。模拟结构的其余部分特别令人感兴趣&#xff0c;因此可以进行物理复制&#xff0c;以揭示粘弹性、屈曲、速率相关特性或其他非线性效应的影…

[Linux] GRUB引导 学习笔记(一)

目录 概念 2.1 BIOS 2.2 UEFI 2.3 MBR与GPT 2.3.1 MBR 2.3.2 GPT 2.3.3 总结 2.4 GRUB GRUB2和GRUB Legacy区别 进入GRUB命令行 命令 GRUB工具命令 GRUB2配置 1.主要配置文件 2. 通过/etc/default/grub文件生成grub.cfg 定制GRUB的步骤 概念 BIOS、UEFI、MBR、G…

C++基础——对于C语言缺点的补充(2)

上篇文章中说到&#xff0c;为了解决C语言会出现人为定义的函数和库函数出现重定义的错误&#xff0c;C引入了一个新的概念&#xff0c;即命名空间&#xff0c;通过认为定义命名空间&#xff0c;来解决上述问题。 在本篇文章中&#xff0c;将继续介绍C相对于C语言不足来进行的补…

Fourier分析导论——第4章——Fourier级数的一些应用(E.M. Stein R. Shakarchi)

第 4 章 傅里叶级数的一些应用 Fourier series and analogous expansions intervene very naturally in the general theory of curves and surfaces. In effect, this theory, conceived from the point of view of analysis, deals obviously with the study of arbitra…

基于MSF控制同一热点(局域网)下的其他设备

主要是基于Metasploit&#xff0c;利于msfvenom生成的恶意软件获取目标shell。 我想各位都很熟悉的一个操作&#xff0c;那就是使用虚拟机当攻击机&#xff0c;本地物理机作为靶机&#xff0c;但这样其实并不能很好的反应出现实情况&#xff0c;有点自己攻击自己的感觉。 因此…

pytorch安装1

用豆瓣源安装pytorch1.5.1&#xff08;速度很快&#xff09;-CSDN博客 详情请参考这位神仙的博客 我真的哭死&#xff0c;原来torch都安装好了&#xff0c;好不容易全部加载好了&#xff0c;但是&#xff0c;gpu配不上去&#xff0c;后来发现还是版本的问题版本不匹配具体版本…

JTS: 16 Orientation 方向

这里写目录标题 版本代码 版本 org.locationtech.jts:jts-core:1.19.0 链接: github 代码 public static void main(String[] args) {OrientationUse orientationUse new OrientationUse();orientationUse.test02();}public void test02() {A new Coordinate(2, 1);B new …

数据结构初阶---复杂度的OJ例题

复杂度的OJ例题 一、消失的数字1.思路一2.思路二3.思路三 二、旋转数组1.思路一2.思路二3.思路三 一、消失的数字 数组nums包含从0到n的所有整数&#xff0c;但其中缺了一个。请编写代码找出那个缺失的整数。你有办法在O(N)时间内完成吗&#xff1f; 链接&#xff1a;力扣&…

远程管理SSH服务

一、搭建SSH服务 1、关闭防火墙与SELinux # 关闭firewalld防火墙 # 临时关闭 systemctl stop firewalld # 关闭开机自启动 systemctl disable firewalld ​ # 关闭selinux # 临时关闭 setenforce 0 # 修改配置文件 永久关闭 vim /etc/selinux/config SELINUXdisabled 2、配置…

【深度学习】pytorch——Autograd

笔记为自我总结整理的学习笔记&#xff0c;若有错误欢迎指出哟~ 深度学习专栏链接&#xff1a; http://t.csdnimg.cn/dscW7 pytorch——Autograd Autograd简介requires_grad计算图没有梯度追踪的张量ensor.data 、tensor.detach()非叶子节点的梯度计算图特点总结 利用Autograd实…

Transformer:开源机器学习项目,上千种预训练模型 | 开源日报 No.66

huggingface/transformers Stars: 113.5k License: Apache-2.0 这个项目是一个名为 Transformers 的开源机器学习项目&#xff0c;它提供了数千种预训练模型&#xff0c;用于在文本、视觉和音频等不同领域执行任务。该项目主要功能包括&#xff1a; 文本处理&#xff1a;支持…

【Redis】hash数据类型-常用命令

文章目录 前置知识常用命令HSETHGETHEXISTSHDELHKEYSHVALSHGETALLHMGET关于HMSETHLENHSETNXHINCRBYHINCRBYFLOAT 命令小结 前置知识 redis自身就是键值对结构了&#xff0c;哈希类型是指值本⾝⼜是⼀个键值对结构&#xff0c;形如key"key"&#xff0c;value{{field1…