目录

一.理论知识

二.优化(滚动数组)

三.题(取与不取,总和小于等于某一值<容量>)


01背包问题,主要是当选到第i个物品取与不取,对结果的影响。取了会不会使结果增大。

一.理论知识

背包问题主要是描述一个限重背包,物品有各自的重量和价值,问被背包能放下最大的价值是多少。

01背包问题于其它背包问题的区别是物品只有一个,后序会讲到,完全背包问题。

简单问题描述:

有n个物品,每件物品的重量为W[ i ],价值为V[ i ]。现有以容量为M的背包,问如何选取物品放入背包,使背包内的物品价值总量最大。其中每件物品只有一件。

暴力解法:枚举出选取物品的并且物品重量和小于等于背包容量的所有情况,利用回溯算法。因为物品只有两种状态,取或者不取,时间复杂度为O(2^n)。显然是很糟糕的。

动态规划01背包解法
        从动态规划基础四个角度分析:由于最后的得到的价值会与背包的容量和物品信息有关,所以定义一个二维数组dp[ i ][ j ]来保存结果。i表示第i个物品,j表示背包有j的容量。

        状态定义dp[ i ][ j ]。放入第i个物品背包容量为j时的最大价值。

        转移方程:当到了第i个物品,我们有两种情况可以选择。

1.放入背包,此时我们需要在背包里腾出第i个物品的重量W[ i ],这样才放得下第i个物品。此时的价值为 dp[i-1][ j-W[ i ] ] + V[ i ]。首先我们先得到没放第i个物品时,并且为第i个物品腾出空间后背包里的最大价值dp[i-1][ j-W[ i ] ],加上第i个物品的价值,就是总价值。

2.不放入背包。此时背包的价值并不会发生变化,就是上一次的价值,dp[ i-1 ][ j ]。

        不知道你会不会有跟我一样的问题,为什么不直接就放入,就是在上一次的价值上加上第i个物品价值就好了?

        因为直接放入此时容量就是j了,已经到了最大容量,已经放不下了,所以必须为第i个物品腾出空间。

        转移方程:

                放得下:max( dp[ i-1 ][ j ],dp[i-1][ j-W[ i ] ] + V[ i ])。

                放不下:dp[ i-1 ][ j ]。

        初始化:dp[ 0 ][ j ]=0, dp[ i ][ 0 ]=0。

dp[ 0 ][ j ],相当于空包,没有放物品。

dp[ i ][ 0 ],相当于假包,放不下物品。

        返回值:dp[ n ] [ m ]。有n个物品放入容量为m的包的最大价值。

举个例子,帮助理解,背包的容量为M=8。

物品 A B C D E
大小 3 1 10 2 4
价值 2 10 100 1 3

此时建立的dp数组:

完整dp数组:

01背包问题可以理解为,将所有背包容量情况的最大价值求出来,为最后得到结果利用。

代码在得到显示。

二.优化(滚动数组)

对于上面这种情况,其实是可以进行优化的。

其中的二维数组每一行代表的意义都是一样的,只是代表的放入的物品不同,并且每一层的结果都是由上一层的当前列或者前面的列得到的。

优化情况,我们只需要用一个一维数组来保存结果,每次更新数组里的结果来得到放入i时的最新结果。

注意:得到结果时需要从后往前遍历

原因:

1.后面的值需要前面的值来得到,如果从前完后遍历,前面的值就改变了了,得不到正确的结果。

        2.从后往前遍历每一件物品只选择了一次。如果从前往后遍历,每件前面的值已经更新了,索命每件物品可以选择多次。

代码在得到显示。

三.题(取与不取,总和小于等于某一值<容量>)

牛客:NC145 01背包,https://www.nowcoder.com/practice/2820ea076d144b30806e72de5e5d4bbf?tpId=196&&tqId=37561&rp=1&ru=/activity/oj&qru=/ta/job-code-total/question-ranking

已知一个背包最多能容纳物体的体积为V

现有n个物品第i个物品的体积为viv 第i个物品的重量为wi

求当前背包最多能装多大重量的物品

通过上面的理论基础,得到下面的代码:

class Solution {
public:int knapsack(int V, int n, vector<vector<int> >& vw) {// write code here//初始化,dp[0][j]=0,dp[i][0]=0vector<vector<int>> dp(n+1,vector<int>(V+1,0));//先遍历物品for(int i=1;i<=n;i++){//再遍历背包容量for(int j=1;j<=V;j++){//放得下if(j>=vw[i-1][0])dp[i][j]=max(dp[i-1][j],dp[i-1][j-vw[i-1][0]]+vw[i-1][1]);//放不下elsedp[i][j]=dp[i-1][j];}}return dp[n][V];}
};

优化:

class Solution {
public:int knapsack(int V, int n, vector<vector<int> >& vw) {//初始化,没放物品,初始化为0vector<int> dp(V+1,0);//物品for(int i=1;i<=n;i++){//背包容量。倒序遍历for(int j=V;j>0;j--){//放得下if(j>=vw[i-1][0]){dp[j]=max(dp[j],dp[j-vw[i-1][0]]+vw[i-1][1]);}//放不下就是原来的值}}return dp[V];}
};

力扣416 分割等和子集,https://leetcode-cn.com/problems/partition-equal-subset-sum/

给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

这个题的暴力解法可以使用回溯法,找出是否含有组合值的和相等。

class Solution {
public:void backtracing(vector<int>& nums,int& leftsum,int& rightsum,int startindex,int len,int& flag){if(leftsum==rightsum){//当值相等时,说明存在flag=1;return;}for(int i=startindex;flag==0&&i<len;i++){//一边加上数,一边减数leftsum+=nums[i];rightsum-=nums[i];backtracing(nums,leftsum,rightsum,i+1,len,flag);//回溯leftsum-=nums[i];rightsum+=nums[i];}}bool canPartition(vector<int>& nums) {int leftsum=0;int rightsum=0;int len=nums.size();//计算出总和for(int i=0;i<len;i++){rightsum+=nums[i];}int flag=0;backtracing(nums,leftsum,rightsum,0,len,flag);return flag==1;}
};

但是这样的时间复杂度很高,解决此题时超过了时间限制。

我们还可以使用另外一种解法。

假设sum为集合总和,要在一个集合中找到两个集合和相等的子集说明就要找集合中是否存在子集和等于sum/2。于是这个题可以使用01背包的思想来解。

题目可以转化为:

一个容量为sum/2的背包,第i件物品的价值和容量为nums[ i ],求是否可以找到价值等于sum/2?

为什么物品容量和价值都等于nums[ i ]?

        因为我们要求的是子集和,子集和中的每个元素都是nums[ i ]。所以价值是nums[ i ]。放入背包的物品容量的大小和肯定小于等于背包的容量,当容量与价值相等时,价值的大小肯定也小于等于背包容量。01背包求的是最大价值,肯定是最靠近背包容量的值。在这里求的是最大子集和,判断等不等于sum/2就好了。

从四个角度分析:

状态定义:dp[ i ],子集和为i,时实际凑成的子集和dp[ i ]。

转移方程:和01背包的转移方程一样。只是物品的价值和重量都是nums[ i ]。

                                dp[ j ]=max(dp[ j ],dp[ j - nums[i] ] + nums[i]);

初始化:一开始的不放物品,初始化为0,

返回值:return dp[ sum/2 ]==sum/2。

class Solution {
public:bool canPartition(vector<int>& nums) {int len=nums.size();int sum=0;//求集合总和for(int i=0;i<len;i++){sum+=nums[i];}//如果是奇数,肯定找不到相等的if(sum%2){return false;}int v=sum/2;//初始化vector<int> dp(v+1,0);//物品for(int i=1;i<=len;i++){//背包容量for(int j=v;j>0;j--){if(j>=nums[i-1]){dp[j]=max(dp[j],dp[j-nums[i-1]]+nums[i-1]);}   }}return dp[v]==v;}
};

力扣 1049 最后一块石头的重量 II

有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。

每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0。

题目的意思是,随便取两值,计算差值后重新放到数组中参与计算。最后使得数组中的值最小。

使得最后的值最小,就是要找到y-x值最小,就是要使得x与y的值很接近。将所有取得的x的值相加,所有取得的y值相加。得到了两个结果,使得两结果差值最小,就是要使得两结果都很靠近所有值总和的一半。并且肯定会两个堆中,一个堆的结果大于等于总和一半,另一堆结果小于等于总和一半。

这样就和上面求子集和的题类似了,转化成01背包问题

假设值的总和为sum,背包容量为sum/2,使得物品i价值和容量都等于stones[ i ](物品容量和小于等于背包容量,价值和容量相等,使得得到价值结果也小于等于背包容量,并且是最接近nums的值)

状态定义:dp[ i ]。数值i的最大的子集和。

转移方程: dp[ j ]=max(dp[ j ],dp[ j - stones[i] ] + stones[i]);

初始值: 一开始没放值,初始化为0

返回值:最小差值。

class Solution {
public:int lastStoneWeightII(vector<int>& stones) {int len=stones.size();int sum=0;//求和for(int i=0;i<len;i++){sum+=stones[i];}//背包容量int v=sum/2;vector<int> dp(v+1,0);//最后求出来的值肯定小于等于vfor(int i=1;i<=stones.size();i++){for(int j=v;j>0;j--){if(j>=stones[i-1]){dp[j]=max(dp[j],dp[j-stones[i-1]]+stones[i-1]);}}}//sum-dp[v]为剩下的,减dp[v]就是最小差值return sum-dp[v]-dp[v];}
};

力扣 494 目标和 https://leetcode-cn.com/problems/target-sum/

给你一个整数数组 nums 和一个整数 target 。

向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 :

例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1" 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

这个题,炸一看每个元素不是取与不取的关系,而是每个元素都必须取,并不能联想到10背包。

但是,这个题其实与上面石头一题有点类似,因为每一个数之间只有加减关系。将数组中的数分成两个集合,一个集合的和为leftsum,一个集合的和为rightsum。

这样就转化成了,求数组中元素和为leftsum的方法数,即求数组中能元素和为(sum + target)/2的所有方法数。其中数组中的元素也只有取或者不取的关系,并且总和小于等于leftsum,转化成了01背包问题。

状态定义:数组中得到和为i的方法数,dp[ i ]。

转移方程选择数组中某一数,之前就已经得到了得到和的结果为i的方法数,或者是0,或者不是0,加一个数,再原来的方法数的基础上,会增加方法数,还可能从其它方法得到值为i。

        dp[i]=dp[ i ]+dp[ i - nums[ i ] ];

        等于之前就可以得到值为i的方法数 + 和i - 加的数的值时的方法数。

初始化当需要得到的值为0时,方法数有1个,不选数组中的数。所以初始化dp[ 0 ] = 1。如果初始化为0的话,后面的值全是0了。

返回值:dp[ leftsum ]。值为leftsum时的方法数。

上面其实初始化和转移方程式有点难想的,不怎么好确定价值。

class Solution {
public:int findTargetSumWays(vector<int>& nums, int target) {int sum=0;int len=nums.size();for(int i=0;i<len;i++){sum+=nums[i];}//为奇数,得不到if((sum+target)%2)return 0;//要找到和的数int m=(sum+target)/2;//要求的数都大于总和了 得不到if(m>sum) return 0;vector<int> dp(m+1,0);//初始化,值为0,不选数dp[0]=1;for(int i=1;i<=len;i++){//要等于0,有值等于0的情况,体积等于0的情况for(int j=m;j>=0;j--){if(j>=nums[i-1]){//等于之前就可以使值等于j的方法数,加,现在加一个数可以使值等于j的方法数。dp[j]+=dp[j-nums[i-1]];}}}return dp[m];}
};

注意:里层循环为背包容量,有出现值为0的情况,会影响容量为0的情况。

力扣 474 一和零 https://leetcode-cn.com/problems/ones-and-zeroes/

给你一个二进制字符串数组 strs 和两个整数 m 和 n 。

请你找出并返回 strs 的最大子集的大小,该子集中 最多 有 m 个 0 和 n 个 1 。

如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。

例如:

输入:strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3
输出:4
解释:最多有 5 个 0 和 3 个 1 的最大子集是 {"10","0001","1","0"} ,因此答案是 4 。
其他满足题意但较小的子集包括 {"0001","1"} 和 {"10","1","0"} 。{"111001"} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。

这其实是一个二维01背包问题背包的容量包含了两层含义,0元素的个数和1元素的个数。

可以用一个二维数组来保存出现strs元素中每一个元素出现的0的个数和1的个数。每个元素0和1的个数就是物品的体积,什么是价值呢?因为求的是元素数个数,选一个元素,元素个数加1,所以每个物品的价值为1。

保存结果的dp数组序要用到二维的数组,因为有两个因素影响,一个是0的个数,一个是1的个数。

状态定义:dp[ i ][ j ]。0个数为i,1个数为j的最大元素个数。

转移方程dp[ i ][ j ]=max( dp[ i ] [ j ],dp[ i - sumzero][ j - sumone ]+1)。

得到i个0,j个1时之前的元素个数与选择第k个元素时元素个数的最大值。

初始值:一开始0个0,0个1,元素个数为0。这里时元素个数不是方法数,与上一题不同。

返回值:dp[ m ][ n ]。m个0,n个1时的最大元素个数。

class Solution {
public:int findMaxForm(vector<string>& strs, int m, int n) {//两个维度的01背包问题。背包包含两个容量0的容量。1的容量性质int len=strs.size();//初始化,不选strs里的数,里面的子集元素个数为0vector<vector<int>> dp(m+1,vector<int>(n+1,0));for(int s=0;s<len;s++){int sumzero=0;int sumone=0;int x=strs[s].size();//求出字符串中0和1的个数for(int j=0;j<x;j++){if(strs[s][j]=='0'){sumzero++;}else{sumone++;}}//问的是子集个数,每个数就代表一个,所以价值为1//价值为1 ,物品容量为0和1的个数//要等于0,01个数可能为0for(int i=m;i>=0;i--){for(int j=n;j>=0;j--){if(i>=sumzero&&j>=sumone){dp[i][j]=max(dp[i][j],dp[i-sumzero][j-sumone]+1);}}}}return dp[m][n];}
};

注意:0的个数和1的个数可能为0,会对背包容量为0时产生影响,里层循环需要等于0。

01背包问题(取还是不取呢)相关推荐

  1. 三十四、动态规划解决01背包问题

    一.动态规划算法介绍 动态规划算法的核心思想是:将大问题划分为小问题进行解决,从而一步一步获取最优解的处理算法. 动态规划算法与分治算法类似,其基本思想是将待求解的问题分解成若干个子问题,先求子问题, ...

  2. 动态规划之 0-1背包问题及改进

    有N件物品和一个容量为V的背包.第i件物品的重量是w[i],价值是v[i].求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大.在选择装入背包的物品时,对于每种物品i,只能选择 ...

  3. 自顶向下 与自底向上解决01 背包问题

    01背包问题具体例子:假设现有容量10kg的背包,另外有3个物品,分别为a1,a2,a3.物品a1重量为3kg,价值为4:物品a2重量为4kg,价值为5:物品a3重量为5kg,价值为6.将哪些物品放入 ...

  4. 动态规划的用法——01背包问题

    动态规划的用法--01背包问题 问题主题:著名的01背包问题 问题描述: 有n个重量和价值分别为wi.vi的物品,现在要从这些物品中选出总重量不超过W的物品,求所有挑选方案中的价值最大值. 限制条件: ...

  5. 动态规划套路在最长公共子串、最长公共子序列和01背包问题中的应用

    2019独角兽企业重金招聘Python工程师标准>>> 适合动态规划(DP,dynamic programming)方法的最优化问题有两个要素:最优子结构和重叠子问题. 最优子结构指 ...

  6. 动态规划专题 01背包问题详解【转】

    对于动态规划,每个刚接触的人都需要一段时间来理解,特别是第一次接触的时候总是想不通为什么这种方法可行,这篇文章就是为了帮助大家理解动态规划,并通过讲解基本的01背包问题来引导读者如何去思考动态规划.本 ...

  7. 01背包问题+完全背包问题+多重背包问题

    一 01背包问题 1.1题目 有N件物品和一个容量为V 的背包.放入第i件物品耗费的空间是Ci,得到 的价值是Wi. 求解将哪些物品装入背包可使价值总和最大. 1.2 基本思路 这是最基础的背包问题, ...

  8. 分支界限算法【0-1背包问题】按照优先队列式(LC)分支限界法求解0-1背包问题, 并给出限界函数,并画出该实例的状态空间树。

    目   录 回溯算法[0-1背包问题] 分支界限算法[0-1背包问题] 作业题(期末考试必考) 小结 回溯算法[0-1背包问题] 分支界限算法[0-1背包问题] 解决思路:采用优先队列式分支限界 Ø ...

  9. c语言贪心算法背包问题_GGTalk 中的算法知识 01背包问题

    前几天 GGTalk 发了一期关于算法类的播客,主持人磊子和嘉宾 WAMaker 都分享了很有趣的算法经历.这一系列文会帮你梳理一下在这期电台中,你应该知道的知识点. 这一篇来聊聊博客中 WAMake ...

最新文章

  1. node.js linux shell,bash – Node.js Shell脚本和参数
  2. 2015年第六届蓝桥杯 - 省赛 - C/C++大学A组 - G. 手链样式
  3. python线程状态_Python线程
  4. LeetCode 2134. 最少交换次数来组合所有的 1 II(数组*2 + 滑动窗口)
  5. 炫酷超漂亮辅助网站源码
  6. redis 缓存预热_Redis常见问题总结
  7. 中国内脏痛行业市场供需与战略研究报告
  8. java代码进行短信接收_自动化测试-自动获取手机短信验证码
  9. python if 比较小数浮点数
  10. 怎样开始买基金---我的基金购买体验[转]
  11. 飞象星球落地重庆云阳86所学校,县乡4万学生迎来素质课堂
  12. java程序设计特点_Java编程语言的特点
  13. python制作自己的专属二维码
  14. C语言函数指针使用的一个例子(Win10,VS2022)
  15. LVS均衡负载(三) LVS后端服务健康状态检查
  16. 《机器学习实战》学习第一章
  17. 蓝桥杯算法考前复习要点和归纳总结
  18. 微软面试题:站在地球上的某一点,向南走一公里,然后向东走一公里,最后向北走一公里,回到了原点。地球上有多少个满足这样条件的点?
  19. 直播平台对企业的作用有哪些
  20. 【JSP】jQuery Deferred exception successed is not defined

热门文章

  1. 【电信增值业务学习笔记】9基于智能网的增值业务实现技术和应用
  2. 所谓深度链接(Deep linking)
  3. 坚果云同步linux,#坚果云每日小技巧分享# 你们要的“选择性同步”越狱啦!
  4. java.强引用软引用,java 强引用、软引用、弱引用、虚引用-Go语言中文社区
  5. 怎么批量删除html里的字段,如何从Excel中的字符串中删除所有HTML标签?
  6. Draft - 在线MarkDown写作平台
  7. “安装完成,某些产品无法安装”,完美干净的清楚CAD残留文件。顺利安装CAD。
  8. Presto 运行Shell
  9. 毕业设计 大数据房价预测分析与可视
  10. vue2 图片裁剪上传功能