【算法练习Day35】01背包问题分割等和子集

在这里插入图片描述

​📝个人主页:@Sherry的成长之路
🏠学习社区:Sherry的成长之路(个人社区)
📖专栏链接:练题
🎯长路漫漫浩浩,万事皆有期待

文章目录

  • 01背包问题
  • 分割等和子集
  • 总结:

这一期我们来介绍背包问题,在leetcode中没有纯粹的01背包问题的具体题目,但是有一些可以用到01背包问题思路的题目。

许多的背包问题都是由01背包演化而来的,所以01背包的重要性,应该是不言而喻的

01背包问题

下面说一下01背包具体的题目要求是什么:给定一些物品,物品都具有两个单独的属性,物品重量和物品价值,且每一个物品都只有一个,现给定一个固定容量的背包,要求是如何选取物品填充背包,可以使得背包内物品价值最高?

是这样的一道题,区分背包问题的类型通常是和物品有关,这里要求物品只有一个,这也是01背包的特性所在。

两种解决方案,都是动态规划的,只不过第一种是二维数组实现,第二种是一位数组实现。那么我们为什么不先讲一维数组实现呢?因为二维数组实现是基础,只有搞懂二维数组,才有利于我们取理解一维数组的实现。

仍然根据之前讲的动规五部曲来实现

确定dp数组的含义:dp数组我们之前说了是一个二维数组,那么i和j代表了什么呢?i代表从物品0-i中任取物品,j代表了背包的容量。dp【i】【j】代表了当前取到这个物品时候,背包价值是多少。这是对于该二维数组的一个较为抽象的描述,我们可以采用纸上画出二维数组的方法来帮助理解,就是画一个棋盘格子,行上我们用背包容量去表示,比如从背包容量0到容量j,而列上我们用物品0-i来表示。画出了格子相信大家就可以很好的理解了。

递推公式:我们根据对dp数组的定义可得出,当前背包可以有装入当前物品i和不装入当前物品i两种状态!不装物品i可以表示为dp【i-1】【j】,因为不装物品i和它的上一行同一列对应的价值是相等的,这里如果看不懂一定要回头看一遍dp的定义以及自己画出来的表格。这里你也可以理解为遍历上一层的时候是装物品1,这一层是装了物品1但是没装物品2,这样理解就很好想了。当然你可以理解为,当前背包容量可能不足,装不了物品i。那么装载物品i的情况是什么样的呢?先给出结论装载物品i是dp【i-1】【j-weight【i】】+value【i】。为什么是这样的呢?我们对于dp【i-1】【j-weight【i】】+value【i】的解释是这样的:dp【i-1】是未放入物品i,而【j-weight【i】】相当于为了放入物品i我们要找到一个合适的地方,这就相当于回溯一样,是确定这个背包是否还能装的进去物品i,如果装不进去把多于重量减去,而+value【i】就是计算价格的。

根据这两个数,我们可以推出递推公式

即dp【i】【j】=max(dp【i-1】【j】,dp【i-1】【j-weight【i】】+value【i】)

这里还有要搞清楚的点,为什么要取它们的最大值呢?

不是放入物品就一定是价值最大吗?并不是,放入物品你要有空余的背包容量,我们这里判断的是加入i和不加入i的价格,那么放入i如果本来就有地方放,那肯定是最大价值,那如果没有地方放,需要先扔出一些物品呢?那就不一定谁更大了!

dp数组的初始化:dp数组的初始化也是有讲究的。根据递推公式的推算,以及画图的理解,我们知道我们要求的dp【i】【j】是由它的上一个数和它的左上方的数推理得到的。最左侧一列也就是背包容量为0的那一列,对应的全部物品都无法放入,全都初始化为0,而最上面一行是针对物品0在各个背包容量上能否放入,我们把能够放入的初始化为物品0的价值。其他的位置都可以初始化为0,因为我们的递推公式的缘故,并没有比较有关于它本身的数值,所以其实初始化为什么数字都可以。

遍历顺序:遍历顺序同样有讲究。虽说根据递推公式,基本的遍历顺序应当是从左到右,从上到下。但是我们是遍历二维数组,那么需要两个for循环,是先遍历背包容量,还是先遍历各个物品呢?实际上两种遍历都可以。如果是先遍历物品再遍历背包,那就是以行从左到右依次填满,当求i,j位置时候,它的上面和左上方一定是先被填充的,所以可以出答案。事实上通常采用这种方法,因为便于逻辑上的理解。但是先遍历背包再遍历物品呢?那就是背包先不动,然后走到第二次循环遍历物品,也就是以列从上到下填充,但是当我们求i,j时候,发现它的上方和左上方也是一定有值的,所以说,这两种遍历方式理论上都是可行的!

数组打印:这就没什么好说的了,就是当想不明白,或者出现什么结果上问题了,可以把整个数组打印出来看一看,是哪里出了问题。

下面给出一个可以运行的代码,大家可以跑一下自行体会

void test_2_wei_bag_problem1() {vector<int> weight = {1, 3, 4};vector<int> value = {15, 20, 30};int bagweight = 4;// 二维数组vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));// 初始化for (int j = weight[0]; j <= bagweight; j++) {dp[0][j] = value[0];}// weight数组的大小 就是物品个数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];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() {test_2_wei_bag_problem1();
}

初始化的代码就是先看哪个容量首先能装得下物品0,就放入那个地方然后向后遍历,由于我们的dp数组定义j是背包容量所以会这么写。下一个需要注意的地方是,计算i,j值的时候,当我们判断如果本来总空间都不够放入该物品那么肯定就放不下了,直接让它等于上一个放物品时候的价值。当发现当前要放入的物品,是比总空间小的话,那么可能扔出一些物品还是能放进去的,这个时候我们再比较。

接下来再来介绍,一维数组对于01背包问题的求解,一维数组的解决思路,和二维数组的解决整体思路是一样的,但是要完全看懂二维数组是如何解决的,大概率才能弄的懂一维数组。

一维数组是对二维数组的压缩,将二维数组压缩成一维,然后每次更新一维数组的全部值,以实现对二维数组的每一行的滚动,所以也叫作滚动数组的方法。

dp【j】数组的含义:dp数组的含义是代表了当前容量下所能存储的最大价值。这个数组我们上面也说了,需要反复遍历之后,才能得到最大值,所以它通常都不是一次就确定下来,当前最大值是多少的。我们为什么将里面的变量取为j呢,是为了和上面的二维数组做对比,我们保留下来的实际是二维数组的背包容量这一块,而非物品状态。

递推公式:这里我会好好解释一下为什么递推公式是这样,它是怎么推出来的?先写结论递推公式为:dp【j】=max(dp【j】,dp【j-weight【i】+value【i】)

我们把它压缩为一维数组不代表,物品就不遍历了,物品一定还是要遍历的,不然你如何知道我们放了什么东西进去呢??价格又是多少呢?压缩到一维数组后,我们这个数组每次利用的是,上一次放入东西后的总价格,和当前我们如果放入物品i得到的价格进行对比,我们比较的上一次的dp【j】这也就代表了二维数组中的上一层,一维数组将它保留到本层,然后比较之后再将当前格子价格给覆盖掉。完成了更新滚动数组,这里希望大家能够仔细地体会一下一维数组是如何巧妙地得到二维数组的上一行!正是我们上一次遍历填数后的一维数组!解释清楚了这一点,那么后面的+value【i】,【j-weight【i】】也不难理解了,这些都是讲二维数组讲过的东西,不再赘述。

dp数组初始化:初始化dp【0】是一定为0的,因为无论是哪一层,0背包容量肯定装不了东西,价值就自然为0,那其他的位置呢?实际上也初始化为0,这是由于我们的物品价格一定是正数,且一维数组需要比较当前dp【j】的位置,如果你初始化给的数过大,它影响到的是你第一次填数,过大会导致第一次填数填不进去,进而影响了后面的价格覆写。虽然你只看递推公式,感觉的是填数比较的是上一层的数,和初始化无关,但是实际上你没有注意到,初始化数字会影响到你第一层的价格填写,应当引起重视,当过了第一层价格填写后,初始化便不对之后的滚动数组产生影响了。

遍历顺序:遍历顺序,还是从左往右从上到下的遍历吗?还是先遍历物品还是背包容量都是可以的吗?这里就相当的有讲究了!遍历顺序一定是也只能是先遍历物品,再遍历背包,且背包需要从后往左遍历。因为什么呢?这是由于滚动数组的特性,是要根据上一层填写的数值来更新本层循环的数值,但是如果我们要从左到右遍历背包的话,第一个数据刚更新完,我们更新第二个数据此时背包如果有空,它会更新为第二个max比较的部分,这样会使第二个数据过早的应用第一个数据,也就是说,本来我们更新第二个数据时候,用到的可能是第一个数据,但是这个数据必须是上一层的,也就是旧的数据,但是你正向遍历背包,第二个数据应用上的就是刚刚更新完的数据,也就是本层的数据,这里好好体会一下。我们可以自己模拟一下,如果是正向遍历背包,我们在第一次的填数时候,假设物品的重量为:1,3,4物品价值为:15,20,30。最大背包容量为4。如果按照这种情况,那么第一次更新背包为1时候,放入物品0,价值为15,背包容量为2时候,根据递推公式可得:dp【2-weight【0】】+value【0】得到背包容量为2时候,价值为30。实际上不可能得到这么多,这相当于把物品0连续放入两次,但是每个物品只有一个,这显然不符题意。所以正确的做法是从后向前遍历背包。

那为什么两层循环,遍历的东西,不能够像二维数组那样可以颠倒顺序呢?

还是和滚动数组特性有关,二维数组可以颠倒顺序是因为我们并不需要担心当前层的数据会覆盖上一层的数据,因为二维数组的各个层次之间是彼此分隔开的,而一维数组不一样,我们本来先遍历物品是使物品不动,通过改变背包容量来更新价值,然后再看第二个物品,第三个物品,是否还可以加入到背包内,使得当前背包价值更高。如果是先遍历背包呢?那么背包容量不变,将物品依次放入,更新价值。乍一看好像思路也没有毛病,但是实际上地推公式是要在装入第二个物品的时候,要比较上一个旧数据,但是我们在当层循环是固定背包容量的,我怎么能改变其他背包的数据呢?这一点在第一次填数时候体现的十分明显,我们背包放第二个物品需要找到,没放第二个物品的起始条件,但实际上那个空我们还没有加入东西呢!!就已经连续更新这个容量的背包了!它和第一种遍历方法完全不一样,第一种是将各种背包容量先更新一次,然后放第二个物品再更新一次全部背包容量,而这种遍历方法是将一个背包容量固定死,一直更新,一直放东西,然后才能去更新其他容量。刚看到这的朋友可能不是十分理解,说的有些抽象,最好的解决办法,就是将代码放到你的编译器里调试一遍,你就能深刻的体会到,究竟有何不同了!

给出一维数组实现的代码:

void test_1_wei_bag_problem() {vector<int> weight = {1, 3, 4};vector<int> value = {15, 20, 30};int bagWeight = 4;// 初始化vector<int> dp(bagWeight + 1, 0);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]);}}cout << dp[bagWeight] << endl;
}int main() {test_1_wei_bag_problem();
}

信息量很大,希望大家看完分析可以有所收获。

下面我们来看一道根据具体的题目应用01背包的实例。

分割等和子集

416. 分割等和子集 - 力扣(LeetCode)

这道题实际上想到用01背包来解的思路,我觉得是有点难度的,并不是实现时候的思路有多难,而是想到用这种思路来解答,是有一些难度的。

分析题目,我们要将一个数组分成两部分,使两部分数组的数据和相等。那么最好是要想到我们可以使一个数组数据和等于整个数组数据和的一半,要想到这一点,才能好解题目。

这里数组的数据就同时代表了01背包里物品的重量和价格,想出这一点是用上背包模板的关键。我们可以用dp数组来模拟,然后用数据来同时表示重量和价格,最后我们来看dp【j】==j这个时候就说该数组可以被分割。为什么?因为重量和价值相等,dp数组表示的是当前重量的最大价值,这里相等的话,也就是说的dp【j】==j了,重量和价值是一样的。

dp数组的含义和递推公式以及遍历顺序完全和01背包一样。初始化也是全都变成0。

class Solution {
public:bool canPartition(vector<int>& nums) {vector<int>dp(10001,0);int sum=0;for(auto i:nums)sum+=i;if(sum%2==1)return false;int target=sum/2;for(int i=0;i<nums.size();i++){for(int j=target;j>=nums[i];j--){dp[j]=max(dp[j],dp[j-nums[i]]+nums[i]);}}if(dp[target]==target)return true;else return false;}
};

当我们发现,给定数组和无法准确的分成一半,也就是说有余数的话,那么说明怎么分也不可能将数组切成数组和相等的两部分了。可以直接return了。

总结:

今天我们完成了01背包问题、分割等和子集两道题,相关的思想需要多复习回顾。接下来,我们继续进行算法练习。希望我的文章和讲解能对大家的学习提供一些帮助。

当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~

在这里插入图片描述

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

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

相关文章

Proteus仿真--基于51单片机的按键控制LED仿真(仿真文件+程序)

本文主要介绍基于51单片机的按键控制LED仿真&#xff08;完整仿真源文件及代码见文末链接&#xff09; 本仿真文件主要涉及4个按键&#xff0c;其中&#xff1a; K1按键的逻辑是——逐个点亮 K2按键的逻辑是——上四个点亮 K3按键的逻辑是——下四个点亮 K4按键的逻辑是——关…

HarmonyOS鸿蒙原生应用开发设计- 元服务(原子化服务)图标

HarmonyOS设计文档中&#xff0c;为大家提供了独特的元服务图标&#xff0c;开发者可以根据需要直接引用。 开发者直接使用官方提供的元服务图标内容&#xff0c;既可以符合HarmonyOS原生应用的开发上架运营规范&#xff0c;又可以防止使用别人的元服务图标侵权意外情况等&…

Http代理与socks5代理有何区别?如何选择?(一)

了解SOCKS和HTTP代理之间的区别对于优化您的在线活动至关重要&#xff0c;无论您是技术娴熟的个人、现代互联网用户还是企业所有者。在使用代理IP时&#xff0c;您需要先了解这两种协议之间的不同。 一、了解HTTP代理 HTTP&#xff08;超文本传输协议&#xff09;代理专门设计…

【Java 进阶篇】Java中的响应输出字节数据

在Java Web应用程序开发中&#xff0c;处理响应是一个常见的任务。有时&#xff0c;您可能需要向客户端发送字节数据&#xff0c;而不仅仅是文本或HTML内容。这可以用于传输各种内容&#xff0c;如图像、文件、视频等。本文将详细介绍如何在Java中使用Response对象输出字节数据…

在 Typescript 项目中使用 cdn 加载的js插件没有类型声明

先上一段同事写得代码, 此处动态的插入了 MathJax 这个 js 插件, 我不知道为什么如此编写, //ts-ignore 此处不知道为什么如此调用, 只能使用 ts-ignore 忽略dynamicLoadingJs("//xxx.com/latex/MathJax.js?configTeX-AMS_HTML", () > {MathJax.Hub.Config({exte…

[Docker]四.Docker部署nodejs项目,部署Mysql,部署Redis,部署Mongodb

一.部署nodejs项目,映射端口,挂载数据卷 可以到https://hub.docker.com/去搜索node镜像,然后下载,也可以直接通过docker pull node下载镜像,然后用这个node镜像启动容器node,这样系统就集成了node服务了,在这里挂载www/node目录到容器中,并指定端口映射,运行nodejs程序,安装npm…

【机器学习合集】模型设计之残差网络 ->(个人学习记录笔记)

文章目录 模型设计之残差网络1. 什么是残差结构1.1 网络加深遇到的优化问题1.2 short connect技术 2. 残差网络及有效性理解2.1 残差网络 3. 残差网络的发展3.1 密集残差网络3.2 更宽的残差网络(wide resnet)3.3 分组残差网络3.4 Dual Path Network3.5 加权残差网络3.6 预激活残…

CSS3网页布局基础

CSS布局始于第2个版本&#xff0c;CSS 2.1把布局分为3种模型&#xff1a;常规流、浮动、绝对定位。CSS 3推出更多布局方案&#xff1a;多列布局、弹性盒、模板层、网格定位、网格层、浮动盒等。本章重点介绍CSS 2.1标准的3种布局模型&#xff0c;它们获得所有浏览器的全面、一致…

HTML表格

HTML表格&#xff1a; HTML表格是由<table>标签来定义。HTML表格式一种用于结构化数据的标记语言元素。每个表格均有若干行&#xff08;由<tr>B标签定义&#xff09;&#xff0c;每行被分割为做干列&#xff08;由<td>标签定义&#xff09;。表格可以包含标…

第22期 | GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练 Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大型语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以…

Java架构师知识产权与标准化

目录 1 导学2 知识产权概述3 保护期限4 知识产权人的确定4 侵权判断5 标准划分想学习架构师构建流程请跳转:Java架构师系统架构设计 1 导学 2 知识产权概述 知识产权是指公民、法人、非法人单位对自己的创造性智力成果和其他科技成果依法享有的民事权。是智力成果的创造人依…

Web渗透编程语言基础

Web渗透初学者JavaScript专栏汇总-CSDN博客 Web渗透Java初学者文章汇总-CSDN博客 一 Web渗透PHP语言基础 PHP 教程 | 菜鸟教程 (runoob.com) 一 PHP 语言的介绍 PHP是一种开源的服务器端脚本语言,它被广泛用于Web开发领域。PHP可以与HTML结合使用,创建动态网页。 PHP的特…

vue3中,使用html2canvas截图包含视频、图片、文字的区域

需求&#xff1a;将页面中指定区域进行截图&#xff0c;区域中包含了图片、文字、视频。 第一步&#xff0c;先安装 npm install html2canvas第二步&#xff0c;在页面引入&#xff1a; import html2canvas from html2canvas;第三步&#xff0c;页面使用&#xff1a; 1&…

【OpenCV实现图像:用Python生成图像特效,报错ValueError: too many values to unpack (expected 3)】

文章目录 概要读入图像改变单个通道黑白特效颜色反转将图像拆分成四个子部分 概要 Python是一种功能强大的编程语言&#xff0c;也是图像处理领域中常用的工具之一。通过使用Python的图像处理库&#xff08;例如Pillow、OpenCV等&#xff09;&#xff0c;开发者可以实现各种各…

纳米银线 纳米银纳米线 平均直径: 50-100nm

&#xff08;西&#xff09;纳米银线 &#xff08;安&#xff09;含量&#xff08;%&#xff09;&#xff1a;99.9 &#xff08;瑞&#xff09;平均直径: 50-100nm &#xff08;20nm 30nm 60nm &#xff09; &#xff08;禧&#xff09;长度&#xff1a;10um …

龙迅视频转换IC LT6711GX适用于HDMI2.1转TPYE-C/DP1.4/EDP功能应用

1.描述 应用功能&#xff1a;LT6711GX适用于HDMI2.1转TPYE-C/DP1.4/EDP 分辨率&#xff1a;最高支持8K30HZ或8K60Hz压缩数据 工作温度范围&#xff1a;−40C to 85C 产品封装&#xff1a;QFN88 &#xff08;10*10&#xff09; 最小包装量&#xff1a;1680PCS 2.产品应用市场 •…

Spring面试题:(二)基于xml方式的Spring配置

xml配置Bean的常见属性 id属性 name属性 scope属性 lazy-init属性 init-method属性和destroy属性 initializingBean方法 Bean实例化方式 ApplicationContext底层调用BeanFactory创建Bean&#xff0c;BeanFactory可以利用反射机制调用构造方法实例化Bean&#xff0c;也可采用工…

jeecg-uniapp 杂七杂八数据

uniapp 点击事件 tap: 单击事件 confirm: 回车事件 blur:失去焦点事件 touchstart: 触摸开始事件 touchmove: 触摸移动事件。 touchend: 触摸结束事件。 longpress: 长按事件。 input: 输入框内容变化事件。 change: 表单元素值变化事件。 submit: 表单提交事件。 scroll: 滚动…

MySQL笔记--SQL语句

目录 1--SQL的通用语法 2--SQL语句的分类 3--DDL语句 3-1--数据库操作 3-2--表操作 3-3--数据类型 3-4--修改和删除 4--DML语句 4-1--插入数据 4-2--修改数据 4-3--删除数据 5--DQL语句 5-1--基本查询 5-2--条件查询 5-3--聚合函数 5-4--分组查询 5-5--排序查…

javaEE -13(6000字CSS入门级教程 - 2)

一&#xff1a;Chrome 调试工具 – 查看 CSS 属性 首先打开浏览器&#xff0c;接着有两种方式可以打开 Chrome 调试工具 直接按 F12 键鼠标右键页面 > 检查元素 点开检查即可 标签页含义&#xff1a; elements 查看标签结构console 查看控制台source 查看源码断点调试ne…