代码随想录day35--动态规划的应用2||01背包理论基础、携带研究材料

01背包理论基础

有n件物品和一个最多能背重量为w的背包。第i件物品的重量是weight[i],得到的价值为

value[i]。每件物品只能用一次,将这些物品装入背包里物品价值总和最大。

这是很标准的背包问题,很多同学看到后很自然的就想到了背包,我们要知道为什么用背包问题,也就是动态规划求解,如果使用回溯算法进行求解,搜索出所有的情况,那么时间复杂度就是:

O(n^2),这里的n表示物品数量。

所以暴力的解法是指数级别的时间复杂度,进而需要动态规划的解法来进行优化

下面,举一个经典的背包问题的例子:

背包最大重量为4.

物品为:

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

问背包能背的物品最大价值是多少?

首先使用二维dp数组解决

依旧还是动态规划五部曲

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

dp[i][j]表示下标为[0-i]的物品中任意取,放进容量为j的背包,价值总和最大是多少

可以结合图表来分析二维数组的定义

dp数组的定义,一定要明确,因为之后的步骤都在围绕dp数组的含义进行分析和操作

如果有些想不通,或者懵逼,那就回顾一下i代表什么,j又代表什么

2.确定递推公式

根据dp[i][j]的含义,可以推导出,可以由两个方向推导出dp[i][j]

·不放物品:由dp[i-1]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]就是

dp[i-1][j]。(其实就是当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以背包内的价值依旧和前面相同)

·放物品:由dp[i-1][j-weight[i]]推出,dp[i-1][j-weight[i]]为背包容量为j-weight[i]的时候不放物品i的最大价值,那么dp[i-1][j-weight[i]]+value[i],就是背包放物品i得到的最大价值

所以递归公式: dp[i][j] = max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);

3.dp数组如何初始化

关于初始化,一定要和dp数组的定义吻合,否则到递推公式的时候就会越来越乱

首先从dp[i][j]的定义出发,如果背包容量j为0的话,即dp[i][0],无论选取哪些物品,背包价值一定为0.如图

从状态转移方程中可以看出,i是由i-1推导出的,那么i为0的时候一定要初始化

dp[0][j],即:i为0,存放编号0的物品的时候,各个容量的背包所能存放的最大价值。

那么很明显当j < weight[0]的时候,dp[0][j]应该是0,因为背包容量比编号0的物品重量还小。

当j >= weight[0]时,dp[0][j]应该是value[0],因为背包容量放足够放编号0物品

所以初始化代码如下:

for (int i = 0 ; i < weight[0]; i++) {  
    dp[i][0] = 0;
}
for (int j = weight[0]; j <= bagweight; j++) {
    dp[0][j] = value[0];
}

dp[i][0]和dp[0][j]都已经初始化完毕,然后就开始其他下标的初始化,其实已经不用再继续初始化了,因为从递推公式来看,已经可以知道,无论初始化任何值,都会被覆盖

4.确定遍历顺序

这道题中,有两个遍历维度:物品与背包重量

那么问题来了,是先比哪里物品还是先遍历背包重量呢?

其实都可以,但是先遍历物品会更好理解

5.举例推导dp数组

先看看推导对应的数值,如图:

建议大家在自己的纸上推导一遍,看看每个元素是不是这样

做动态规划的题目,最好的过程就是自己在纸上推导一遍,如果推导明白了,代码写出来就算有问题,只要把dp数组打印出来,对比一下和自己推导的有什么差异,很快的可以发现问题了

以上,就是求解01背包问题,使用二维dp数组的求解过程了

一维dp数组(滚动数组)

对于背包问题,其实状态是可以压缩的

在使用二维数组的时候,递推公式:dp[i][j] = max(dp[i-1][j-weight[i]]+value[i])

其实可以发现如果把dp[i-1]那一层拷贝到dp[i]上,表达式完全可以是:dp[i][j] = max(dp[i][j],dp[i-weight[i]]+value[i];

所以我们发现就可以使用一维数组

这就是滚动数组的又来,需要满足的条件是上一层可有重复利用,直接拷贝到当前层

动规五部分分析如下:

1.确定dp数组的定义

dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]

2.一维dp数组的递推公式

dp[j]为容量为j的背包所背的最大价值,那么如何推导dp[j]呢

dp[j]可以通过dp[j - weight[i]]推导出来,dp[j - weight[i]]表示容量为j - weight[i]的背包所背的最大价值。

dp[j - weight[i]] + value[i] 表示 容量为 j - 物品i重量 的背包 加上 物品i的价值。(也就是容量为j的背包,放入物品i了之后的价值即:dp[j])

此时dp[j]有两个选择,一个是取自己dp[j] 相当于 二维dp数组中的dp[i-1][j],即不放物品i,一个是取dp[j - weight[i]] + value[i],即放物品i,指定是取最大的,毕竟是求最大价值

所以递推公式为:dp[j] = max(dp[j],dp[j-weight[i]]+value[i])

3.一维数组如何初始化

dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j],那么dp[0]就是0,因为背包容量为0,所以最大容量就算0

dp数组在推导的时候一定取的是价值最大的数,如果题目给的价值都是正整数,那么非0下标都初始化为0就可以了

这就不会被初始值覆盖了

4.一维dp数组遍历顺序

代码如下

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

需要注意的是,这里的写法,与比哪里背包的顺序是不一样的

二维数组遍历的时候,背包容量是从小到大,而一维数组遍历的时候,背包是从大到小

一维数组中倒序遍历是为了保证物品i只被放入了一次。如果是正序遍历,那么物品0就会被重复加入多次

那么问题来了,为什么二维dp数组是时候不使用倒序呢?

因为对于二维dp,dp[i][j]都是通过上一层即 dp[i-1][j]计算而来,这一层的的dp[i][j]不会被覆盖

还有一个问题,不论是二维数组还是一维数组,都是遍历物品和遍历背包容量,

二维数组中可以使用遍历物品嵌套遍历背包容量,以及遍历背包容量嵌套遍历物品遍历

那么一维数组中是否可以呢?答案是不可以

一维数组中,背包容量一定要倒序遍历,所以一定是遍历物品嵌套遍历背包容量

5.举例推导dp数组

与二维dp数组一致


以上就是01背包问题求解的两种方法,接下来,让我们使用例题来进一步掌握

携带研究材料

题目描述

小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据不同的空间,并且具有不同的价值。 

小明的行李空间为 N,问小明应该如何抉择,才能携带最大价值的研究材料,每种研究材料只能选择一次,并且只有选与不选两种选择,不能进行切割。

输入描述

第一行包含两个正整数,第一个整数 M 代表研究材料的种类,第二个正整数 N,代表小明的行李空间。

第二行包含 M 个正整数,代表每种研究材料的所占空间。 

第三行包含 M 个正整数,代表每种研究材料的价值。

输出描述

输出一个整数,代表小明能够携带的研究材料的最大价值。

输入示例
6 1
2 2 3 1 5 2
2 3 1 5 4 3
输出示例
5

解题思路就是按照我们之前说的基础理论部分进行推导,这里就不做过多赘述了

代码如下

#include <iostream>
#include <vector>

using namespace std;

int n, bagweight;//bagweight表示背包容量
void solve() {
	vector<int> value(n, 0);//每个物品所占空间
	vector<int> weight(n, 0);//每个物品价值
	for (int j = 0; j < n; j++)
		cin >> weight[j];
	for (int i = 0; i < n; i++)
		cin >> value[i];

	vector<vector<int>> dp(weight.size(), vector<int>(bagweight+1, 0));//定义dp数组,dp[i][j]代表行李箱空间为j情况下,从下标为[0,i]的物品中能达到的最大值

	for (int j = weight[0]; j <= bagweight; j++)//初始化,dp[i][0]已经在数组定义中被初始化为0
		dp[0][j] = value[0];
	for (int i = 1; i < weight.size(); i++) {//遍历物品
		for (int j = 0; j <= bagweight; j++) {//遍历容量
			if (j < weight[i]) dp[i][j] = dp[i - 1][j];//如果装不下就继承dp[i-1][j]的值
			else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
			//如果装的下,就根据状态转移方程进行更新
		}
	}
	cout << dp[weight.size() - 1][bagweight] << endl;
}
int main() {
	while (cin >> n >> bagweight) {
		solve();
	}
	return 0;
}

这是二维dp数组进行推导的解题方法,下面是一维dp数组经推导的解题方法

代码如下

#include <iostream>
#include <vector>

using namespace std;

int main() {
	int M, N;
	cin >> M >> N;

	vector<int> costs(M);
	vector<int> values(M);

	for (int i = 0; i < M; i++)
		cin >> costs[i];
	for (int j = 0; j < M; j++)
		cin >> values[j];

	vector<int> dp(N + 1, 0);//初始化为0

	for (int i = 0; i < M; i++) {//遍历每个类型的研究材料
		for (int j = N; j >= costs[i]; j--) {//从N给空间开始减少当前研究材料所占空间
			dp[j] = max(dp[j], dp[j - costs[i]] + values[i]);
		}
	}

	cout << dp[N] << endl;
	return 0;
}

总结

·我们可以发现一维dp数组的写法比较直观,而且空间复杂度还降低了一个数量级

·01背包问题的理解基础就已经说完了,从dp数组定义、递推公式、初始化、遍历顺序到二维数组到一维数组都深刻的剖析了一遍,大家要将这篇内容吃透,后面再做01背包类型的题目就会很简单。我们是有着自己的思考,一步一步推导,了解了他的本质,代码的方方面面都在自己的掌握之下

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

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

相关文章

【Linux学习】Linux 的虚拟化和容器化技术

˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如…

少儿编程 2024年3月电子学会图形化编程等级考试Scratch一级真题解析(选择题)

2024年3月scratch编程等级考试一级真题 选择题&#xff08;共25题&#xff0c;每题2分&#xff0c;共50分&#xff09; 1、单击下列哪个按钮&#xff0c;能够让舞台变为“全屏模式” A、 B、 C、 D、 答案&#xff1a;C 考点分析&#xff1a;考查scratch平台的使用&…

java中Date类,SimpleDateFormat类和Calendar类

Date类 public Date() 创建一个Date对象&#xff0c;代表的是系统当前此刻的日期时间 public Date(long date) Constructs a Date object using the given milliseconds time value. 把时间毫秒值转变成Date日期对象 public void setTime(long date) Sets an existing Date ob…

【爬虫开发】爬虫从0到1全知识md笔记第3篇:数据提取概要,知识点【附代码文档】

爬虫开发从0到1全知识教程完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;爬虫课程概要&#xff0c;爬虫基础爬虫概述,,http协议复习。requests模块&#xff0c;requests模块1. requests模块介绍,2. response响应对象,3. requests模块发送请求,4. request…

接口练习题目

练习一 1、声明接口Eatable&#xff0c;包含抽象方法public abstract void eat(); 2、声明实现类中国人Chinese&#xff0c;重写抽象方法&#xff0c;打印用筷子吃饭 3、声明实现类美国人American&#xff0c;重写抽象方法&#xff0c;打印用刀叉吃饭 4、声明实现类印度人Indi…

深入Tauri开发——从环境搭建到项目构建

深入Tauri开发——从环境搭建到项目构建 开启你的Tauri桌面应用开发之旅&#xff08;续&#xff09; 经过上一篇文章的基础介绍&#xff0c;现在让我们更进一步&#xff0c;详细阐述如何在Windows和macOS平台上顺利搭建Tauri应用所需的开发环境&#xff0c;并指导您从创建项目…

Python搭建编程环境-安装Python3解释器

✅作者简介&#xff1a;CSDN内容合伙人、新星计划第三季Python赛道Top1&#x1f3c5; &#x1f525;本文已收录于Python系列专栏&#xff1a;零基础学Python &#x1f4ac;订阅专栏后可私信博主进入Python学习交流群&#xff0c;进群可领取Python视频教程以及Python相关电子书…

数据结构——图的应用(最小生成树,最短路径,拓扑排序,关键路径)

目录 1.最小生成树 1.概念回顾——生成树 2.最小生成树概念 2.构造最小生成树 1.MST性质 2.Prim算法 3.Kruskal 算法 4.两种算法比较 3.最短路径 1.两点间最短路径 2.某源点到其它各点最短路径 3.单源最短路径——用Dijkstra算法 4.所有顶点间的最短路径…

算法沉淀——动态规划篇(子数组系列问题(下))

算法沉淀——动态规划篇&#xff08;子数组系列问题&#xff08;下&#xff09;&#xff09; 前言一、等差数列划分二、最长湍流子数组三、单词拆分四、环绕字符串中唯一的子字符串 前言 几乎所有的动态规划问题大致可分为以下5个步骤&#xff0c;后续所有问题分析都将基于此 …

NoSQL之Redis

目录 一、关系型数据库与非关系型数据库 1.关系数据库 2.非关系数据库 2.1非关系型数据库产生背景 3.关系型数据库与非关系型数据区别 &#xff08;1&#xff09;数据存储方式不同 &#xff08;2&#xff09;扩展方式不同 &#xff08;3&#xff09;对事物性的支持不同 …

瑞吉外卖实战学习--14、菜品上传

添加菜品接口 前言效果图1、菜品分类查询接口2、上传图片和下载图片3、创建接收数据的Dto4、创建提交的方法 前言 本项目gitee位置&#xff1a;gitee网址 本篇文章是学习了添加菜品的总结&#xff0c;其中包括菜品分类的接口&#xff0c;图片上传接口&#xff0c;数据整体上传…

Java源值1.5已过时,将在未来所有发行版中删除

1、背景 确认java项目没问题&#xff0c;但是启动的时候&#xff0c;却报错&#xff1a;java: -source 1.5 中不支持 diamond 运算符 2、解决 2.1 2.2 2.3 2.4 2.5

python 插值搜索-迭代与递归(Interpolation Search)

给定一个由 n 个均匀分布值 arr[] 组成的排序数组&#xff0c;编写一个函数来搜索数组中的特定元素 x。 线性搜索需要 O(n) 时间找到元素&#xff0c;跳转搜索需要 O(? n) 时间&#xff0c;二分搜索需要 O(log n) 时间。 插值搜索是对实例二分搜索的改进&#xff0c;…

一致性hash问题(负载均衡原理)

一致性哈希问题 简介 一致性Hash是一种特殊的Hash算法&#xff0c;由于其均衡性、持久性的映射特点&#xff0c;被广泛的应用于负载均衡领域&#xff0c;如nginx和memcached都采用了一致性Hash来作为集群负载均衡的方案。 本文将介绍一致性Hash的基本思路&#xff0c;并讨论其…

程序代码分析工具

文章目录 工具简介和安装DoxygenGraphziv软件安装 工具的运用启动和配置工具分析结果 工具简介和安装 Doxygen Doxygen 是一种用于从 C 、C 、Objective-C 、C# 、Java 和 Python 等语言的源代码中生成文档的工具。它通过解析源代码中的注释来创建详细的 API 文档&#xff0c;…

蓝桥杯23年第十四届省赛-异或和之和|拆位、贡献法

题目链接&#xff1a; 蓝桥杯2023年第十四届省赛真题-异或和之和 - C语言网 (dotcpp.com) 1.异或和之和 - 蓝桥云课 (lanqiao.cn) 参考题解&#xff1a; 蓝桥杯真题讲解&#xff1a;异或和之和 &#xff08;拆位、贡献法&#xff09;-CSDN博客 洛谷P9236 [蓝桥杯 2023 省 A]…

STM32中启用 UART 的特定中断( __HAL_UART_ENABLE_IT函数)开机立即进入中断问题(HAL库)

学习过程中发现启用 UART 的特定中断功能之后&#xff0c;原本应该是等到空闲中断的标志位变化了再进入中断&#xff0c;结果MCU开机就会进入中断&#xff0c;不符合逻辑&#xff0c;所以尝试解决这个问题。 DMA空闲中断 处理 串口接收不定长数据 的文章见以下 原文链接&#…

harmonyOS安装ohpm

下载 下载地址 HUAWEI DevEco Studio和SDK下载和升级 | 华为开发者联盟 初始化 注意&#xff1a;初始化ohpm前&#xff0c;需先完成node.js环境变量配置 1.解压文件&#xff0c;进入commandline-tools-windows-2.0.0.2\command-line-tools\ohpm\bin 2.执行&#xff1a; init.ba…

pycharm调试(步过(Step Over)、单步执行(Step Into)、步入(Step Into)、步出(Step Out))

pycharm调试 pycharm调试 pycharm调试为什么要学会调试&#xff1f;1. 步过 (Step Over)2. 单步执行 (Step Into)3. 步入&#xff08;Step Into&#xff09;4. 步出&#xff08;Step Out&#xff09; 为什么要学会调试&#xff1f; 调试可以帮助初学者更深入地理解编程基础&am…

ROS 2边学边练(11)-- colcon的使用

从此篇开始我们即将进入client library系列&#xff0c;主要包含包的创建、主题、服务、参数、消息等功能的自定义实现&#xff0c;开始真正进入ROS的大门咯。 前言 从ROS 1到ROS 2&#xff0c;对应的构建工具集由 catkin_make -> catkin_make_isolated ->catkin_tools …