当前位置: 首页 > news >正文

代码随想录算法训练营第四十天|01背包 二维 01背包 一维 416.分割等和子集

01背包 二维:

文档讲解:卡码网|46.01背包

视频讲解:https://www.bilibili.com/video/BV1cg411g7Y6

状态:已做出

一、题目要求:

01背包就是有多个物品,每个物品有其自己的价值和重量,每个物品数量只有一个,然后把这些物品放入载重为w的背包里,求出能放入这个背包的物品最大总价值。

二、常见错误:

这道题目最容易出现的错误就是把dp二维数组里的i和j弄错,一旦弄错两者代表的含义就会出现很多的问题,比如初始化的时候把i和j看反了那整个初始化就会弄反。还有一点容易弄错我点,就是在后续的嵌套循环里可能会把物品的价值和重量给弄错,导致后面应该加上物品重量却加上了价值,这里我当时就出错了。

三、解题思路:

这道题目使用动规,这里使用二维数组dp。首先第一步要明白dp[i][j]的含义,要想了解其中的含义,必须要知道物品只有两种情况,一种是将此物品放入背包,另一种是不放,这就是01背包的关键思想, 根据这个思想可以知道,i表示0~i之间任意一个放入背包,j则是背包的重量,dp[i][j]就是[0~i]物品里任意物品放入重量为j的背包里的最大价值。第二步是确定推导公式,根据01背包的特性,每个物品都只有拿与不拿两种状态,所以在遍历到i物品的时候,i物品有两种情况,不拿物品i时dp[i][j]的值就是dp[i-1][j],拿物品i的话,先要把重量j的背包先预留空间给物品i,剩下的背包重量就是j-weight,那么就把value[i]加上dp[i-1][j-weight]的最大价值就可以得到放入物品i的最大价值了。第三步就是初始化dp数组,这里根据推导公式可以知道每个元素都是通过i-1得到的,所以这里必须要对二维数组的第一行进行初始化,那么应该初始化为什么呢?dp这个二维数组行表示物品,列表示背包重量,那么第一行初始化就和地雷零个物品有关,当背包重量不小于背包重量的时候就初始化这个位置元素为value[0],其他的dp元素都初始化为零就行,因为根据推导公式来看,后面的元素都是直接覆盖的,所以其他位置初始化为任何数都行,但第一列所有元素都一定是零,所以所有元素都初始化为零更好。第四步就是确定遍历顺序,这里文档说的很明白,不管是先遍历物品还是先遍历背包重量都是可以的,因为推导公式不会受到遍历方向的影响。最后一步就是按照推导公式举个例子把dp数组元素都推导出来,对比一下代码得出的元素既可。

四、代码实现:

//这里n是背包的最大容量
int dun(vector<int>& wight, vector<int>& value, int n) {int m = value.size();   //将物品个数用变量m来保存//创建二维数组dp,全部元素先初始化为零vector<vector<int>> dp(m, vector<int>(n+1, 0));//将dp数组的第一行所有元素进行初始化,背包容量不小于第零个物品重量的时候将此位置元素赋值为第零个物品的价值for (int j=1; j<=n; ++j) {if (j >= wight[0]) dp[0][j] = value[0];}//开始遍历dp的所有元素,这里i从1开始,因为第零个物品已经初始化for (int i=1; i<m; ++i) {for (int j=1; j<=n; ++j) {if (j >= wight[i]) dp[i][j] = max(dp[i-1][j], dp[i-1][j-wight[i]] + value[i]);else dp[i][j] = dp[i-1][j];}}return dp[m-1][n];   //这里最后返回最后一个物品背包重量为n位置的dp元素,就是得到的答案
}

五、时间复杂度:

时间复杂度:因为是嵌套循环遍历整个二维数组,所以时间复杂度是O(n*m)。
空间复杂度:要创建一个二维数组,所以空间复杂度也是O(n*m)。

六、收获:

通过练习这道经典的01背包,对01背包有了初步的了解,对动规的五个步骤有了更加深入的了解,像这种二维数组的动规,需要对一行或者一列进行初始化,其中遍历循环也需要额外注意,这里二维数组虽然不需要注意遍历循环,但是如果要缩短为一维数组就需要额外注意遍历顺序。

01背包 一维:

文档讲解:卡码网|46.01背包

视频讲解:https://www.bilibili.com/video/BV1BU4y177kY

状态:已做出

一、题目要求:

这次使用一维数组对题目进行求解。


二、常见错误:
这道题目如果使用一维数组dp,在嵌套循环的遍历循环里最容易出现错误, 因为一维数组遍历顺序和二维数组不一样,嵌套的循环必须要从后往前遍历,如果没注意从前向后遍历就会出错。

三、解题思路:

这里一维数组是dp[j],第一步需要了解dp[j]的含义,这个j是背包重量,一维数组是把i给优化掉了,那么dp[j]含义就是背包重量为j的最大物品价值。第二步推出推导公式,按照二维数组的推导公式优化,这里既然把i给去掉了,那么dp[j]就是通过同一行来推出来,如果物品i不放,dp[j]就不用变,如果放物品i那么就是dp[j]=dp[j-weight[i]],因为要给物品i预留空间,剩下的背包重量最大物品价值就是dp[j-weight[i]],这里还需要对比一下两者的最大值,所以最后的推导公式就是dp[j]=max(dp[j],dp[j-weight[i]]+value[i])。第三步就是初始化dp数组,这里统一对dp数组初始化为零,因为dp的元素需要同一行来推出,所以初始化为零才能不影响后续推导。第四步就是确定遍历顺序,这里使用以为数组的话遍历顺序必须要额外注意,首先遍历物品i,再遍历背包重量,这两者不能交换,因为如果先遍历背包重量,dp[j]最后保存的是上一列最后一个物品的最大价值,后面的背包重量就无法推出了。最后举例出一维数组dp的元素和代码得出的对比既可。

四、代码实现:

int dun(vector<int>&wight,vector<int>&value,int n) {   //这里n是背包最大容量int m=value.size();  //将物品的个数用变量m来保存vector<int>dp(n+1,0);   //创建一维数组dp,先对所有元素都初始化为零//下面这个循环对第零个物品进行初始化,不小于第零个物品重量的背包容量最大价值都初始化为第零个物品的价值for(int i=0;i<=n;++i)if(wight[0]<=i) dp[i]=value[0];  //下面这个循环就是遍历所有物品,遍历的先后顺序不能交换for(int i=1;i<m;++i) {  for(int j=n;j>=wight[i];--j) {    //这里循环必须要倒序遍历,不然会出现物品重读放入的情况dp[j]=max(dp[j],dp[j-wight[i]]+value[i]);  }  }  return dp[n];}

五、代码复杂度:

时间复杂度:和二维数组的一样,都是O(m*n),因为一维数组也需要使用嵌套循环。
空间复杂度:这里压缩了空间,将二维变为一维,所以空间复杂度是O(m),m是背包的重量。

六、收获:

这次主要是练习01背包的一维数组解法,这种解法能大大减少空间复杂度,学习一维数组的解法也能对遍历循环有新的认识,之前的动规遍历循环的重要性都没体现出来,而这次01背包的一维数组解法就完美的体现出了遍历顺序的重要性,确定好正确的遍历顺序才能解决一维数组dp的赋值。也学会了怎么将二维数组的推导公式转化为一维数组,还有初始化的改变。

416.分割等和子集:

文档讲解:代码随想录|416.分割等和子集

视频讲解:https://www.bilibili.com/video/BV1rt4y1N7jE

状态:已做出

一、题目要求:

题目要求把数组查分为两个部分,这两个部分元素总和必须相同,相同返回true,不同返回false。

二、常见错误:

这道题目我当时做的时候误把整个数组的元素和作为背包容量,导致最后得出错误代码。

三、解题思路:

这道题目难点就是要找出物品和背包,哪个能作为物品哪个能作为背包。根据题目描述,是把一个数组拆分为两个部分,那么数组元素就是物品,那背包又是什么呢?题目要求两个部分元素和相同,那么元素和就是背包的容量,这里是把拆分的子数组作为背包,这样操作就符合01背包问题了。我自己的代码使用的是bool类型的二维dp数组,而文档给出的解法是int型一维数组dp解法,我使用bool是看到题目要求返回bool类型,所以直接就使用了bool型dp。首先第一步就是确定dp[i][j]的含义,这里dp数组的含义就是在[0~i]之间的元素任意放入容量为j的背包,dp[i][j]就是判断是否符合题意,最后直接返回dp[n-1][target]就可以了。第二步就是找出推导公式,这里我使用了bool类型,那么就不需要考虑元素的价值了,之前那个01背包需要加上价值,这里bool就不用考虑元素价值了,如果不拿元素i,那么dp就是dp[i-1][j],如果拿元素i就是dp[i][j-nums[i]],这里dp要等于dp[j-nums[i]]的前提是j不小于nums[i],如果小于直接等于dp[i-1][j]。第三步就是初始化,我这里使用bool类型初始化是最重要的,如果初始化没做好,就全都会出错,这里先把所有二维数组初始化为false,随后只要初始化第一行就可以了,因为背包容量为零的话一定是false,随意不用额外初始化操作,第一行是考虑第零个元素,所以,只要当前背包容量等于第零个元素就初始化为true,其他都是false,因为根据题目的意思,只要背包容量等于拿到的元素和,就符合要求,所以这里要这么设置。第四步是找到遍历顺序,这里使用二维数组,所以遍历顺序都是从前往后,并且两个循环可以交换。最后一步举例既可。

四、代码实现:

class Solution {
public:bool canPartition(vector<int>& nums) {int n=nums.size();   //这里使用变量n来保存物品数量int sum=0;for(int i=0;i<n;++i) sum+=nums[i];   //这里sum来统计数组和if(sum%2!=0) return false;   //如果数组和不能整除2就一定不会符合题意int target=sum/2;   //这里target作为最大背包容量,根据题意,最大背包容量就是数组和的一半//创建bool类型的二维数组dp,先初始为falsevector<vector<bool>>dp(n,vector<bool>(target+1,false));//这里对第一行进行初始化,只有第零个物品等于背包容量才算符合题意for(int i=0;i<=target;++i) if(nums[0]==i) dp[0][i]=true;//这里是对第一列进行初始化,第一列的数组和为零,那么怎么都符合题意,所以全为truefor(int i=0;i<n;++i) dp[i][0]=true;for(int i=1;i<n;++i) {for(int j=1;j<=target;++j) {//这面就使用推到公式来遍历数组元素if(nums[i]<=j) dp[i][j]=dp[i-1][j] || dp[i-1][j-nums[i]];else dp[i][j]=dp[i-1][j];}}return dp[n-1][target];  //最后直接返回二维数组右下角的元素}
};

五、代码复杂度:

时间复杂度:O(n*m)
空间复杂度:O(n*m)

六、收获:

这道题目就是01背包的应用题,通过找到物品和背包来转化为01背包,这里使用bool类型来解其实我自己觉得挺神奇的,当时一直没想到bool类型的推到公式应该怎么得到,以为bool不行,后面还是问了ai能不能使用bool来解,结果还能用bool解,随后仔细看了推导公式也是终于明白了为什么是这个推导公式,文档给出的解法是一维数组int型dp,更加直观和便捷。通过这道题目的练习了解到了01背包的应用题的解决方式,对这类题有了一定的了解。


文章转载自:

http://p4s9uog4.bypfj.cn
http://uNHDZSoa.bypfj.cn
http://kRZn6KnA.bypfj.cn
http://9KhEFJHW.bypfj.cn
http://ydEKmXNk.bypfj.cn
http://JzIl61ub.bypfj.cn
http://xVz56M2m.bypfj.cn
http://QfDeQgO7.bypfj.cn
http://b2raKFR7.bypfj.cn
http://eGX6WLIQ.bypfj.cn
http://yUfA5IGK.bypfj.cn
http://DcE3KMxQ.bypfj.cn
http://3PKCRilV.bypfj.cn
http://SPPtPtNm.bypfj.cn
http://V60lFipf.bypfj.cn
http://Bb8osAXJ.bypfj.cn
http://5d5qhxwu.bypfj.cn
http://k4dX0mgU.bypfj.cn
http://MqC9k7xO.bypfj.cn
http://ZdbXnJVT.bypfj.cn
http://hJTKNJbm.bypfj.cn
http://XwHkMzFC.bypfj.cn
http://hIZ8hIjP.bypfj.cn
http://YadbIGtr.bypfj.cn
http://PSDZJOJO.bypfj.cn
http://7okZciQb.bypfj.cn
http://BFJIqWOK.bypfj.cn
http://qNgc2Xrq.bypfj.cn
http://s8Wdib5l.bypfj.cn
http://jikGjpro.bypfj.cn
http://www.dtcms.com/a/384764.html

相关文章:

  • 力扣:1547. 切棍子的最小成本
  • LeetCode 2962.统计最大元素出现至少K次的子数组
  • ESP8266无法连接Jio路由器分析
  • 傅里叶变换与现代深度学习
  • 【LeetCode】2785. 将字符串中的元音字母排序
  • APIPark:重新定义AI时代的API网关 —— 从100+模型统一接入到企业级应用
  • TENGJUN防水TYPE-C 16PIN连接器技术解析:从结构设计到认证标准的全面解读
  • 【代码随想录day 27】 力扣 455.分发饼干
  • 云原生与 AI 驱动下的数据工程新图景——解读 DZone 2025 数据工程趋势报告【附报告下载】
  • 从异步到半同步:全面解读MySQL复制的数据一致性保障方案
  • 项目工程中库使用Debug与release
  • IntelliJ IDEA 初学者指南:从零创建并运行 Java 项目
  • 虚拟线程和普通线程的区别
  • 微软发布高危漏洞更新,涉及 Windows、Office、SQL Server 等多款产品
  • IDEA-MyBatis动态sql关联映射
  • 【学习】【js】栈数据结构
  • Coze源码分析-资源库-创建知识库-后端源码-核心技术与总结
  • ArcGIS Pro实现基于 Excel 表格批量创建标准地理数据库(GDB)——高效数据库建库解决方案
  • 在openEuler系统 上安装Go语言开发环境
  • 奈奎斯特频率和采样定理的解释
  • 直播APP集成美颜SDK详解:智能美妆功能的开发实战
  • 基于Matlab GUI的心电信号QRS波群检测与心率分析系统
  • 贪心算法应用:5G网络切片问题详解
  • 【117】基于51单片机GSM智能拐杖老人防跌倒报警器【Keil程序+报告+原理图】
  • Rancher 社区双周报|聚焦 Harvester 新特性:网络、存储与虚拟化全面升级
  • CSS视差旋转动效实战
  • Java 设计模式——单例模式6种写法:从原理到 SpringBoot 落地
  • 【自存】懒汉式单例模式中的多线程经典问题
  • 【第五章:计算机视觉-项目实战之图像分类实战】1.经典卷积神经网络模型Backbone与图像-(4)经典卷积神经网络ResNet的架构讲解
  • 区块链:搭建简单以太坊Geth私有链