欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 新车 > 代码随想录算法训练营第三十天| 01背包问题 二维, 01背包问题 一维 , 416. 分割等和子集

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

2024/10/24 14:26:52 来源:https://blog.csdn.net/m0_62154842/article/details/141130988  浏览:    关键词:代码随想录算法训练营第三十天| 01背包问题 二维, 01背包问题 一维 , 416. 分割等和子集

今天是动态规划学习的第三天,主要的学习内容包括:01背包问题二维数组解法和一维数组解法,以及01背包问题的应用。

 01背包问题 二维

题目链接:46. 携带研究材料(第六期模拟笔试) (kamacoder.com)

首先我们需要了解什么是01背包问题。有n件物品和容量为w的书包,第i件物品的质量为w[i],价值为val[i],每个物品只能使用一次,求解将哪些物品装入背包可以获得最大的价值。

首先对于dp[i][j],我们定义为0-i件物品在背包容量为j时的最大价值,如下图所示:

对于递推公式,我们有两种情况:当我们不放入物品i时,我们的dp[i][j]应该是等于dp[i-1][j]的;当我们需要放入物品i时,应该减少书包的容量,并增加i的价值,所以dp[i][j]=dp[i-1][j-w[i]]+val[i];我们需要在这两种情况中选择价值较大的情况。 

dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

对于初始化,我们需要把第一列初始化为0,因为背包容量为0时肯定啥都装不了;我们需要把第一行的j>=w[0]的情况置为val[0],即当背包容量大于等于第一件物品的质量时,就装入第一件物品;装不下第一件物品就将其赋值为0.

对于遍历顺序,既可以先遍历物品,再遍历背包容量;也可以先遍历背包容量,再便利物品。因为我们dp[i][j]的更新取决于左上角的数值,而这两种遍历方式都会优先把左上角进行赋值,所以在求解时都是可行的。

具体代码实现如下所示:
 

#include<iostream>
#include<vector> 
using namespace std;
int main()
{int n;int bagweight;scanf("%d%d",&n,&bagweight);vector<int> weight(n,0);vector<int> value(n,0);for(int i=0;i<n;i++) scanf("%d",&weight[i]);for(int i=0;i<n;i++) scanf("%d",&value[i]);vector<vector<int>> dp(n,vector<int>(bagweight + 1, 0));for(int i=weight[0];i<=bagweight;i++){dp[0][i]=value[0];}for(int i=1;i<n;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]);}}printf("%d",dp[n-1][bagweight]);}

01背包问题 一维

题目链接:46. 携带研究材料(第六期模拟笔试) (kamacoder.com)

对于01背包问题,我们也可以对二维数组进行状态压缩,从而使用一维数组解决01背包问题。

首页是dp[j]的含义,我们定义为在背包容量最大为j时的最大价值。

对于递推公式,还是分成那两种情况,当不装入物品时,dp[j]=dp[j];当装入物品时,dp[j]=dp[j-w[i]]+val[i],取两种情况的较大值即可。

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

对于遍历顺序,这个就有讲究了。

首先我们先来说一下正确的遍历顺序,外层循环应该是物品的顺序,内层循环应该是背包的容量,并且应该倒序遍历,具体代码如下。

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

首先,我们先来讲一下为什么内层循环一定要倒序遍历。因为我们其实是将二维数组压缩到一维数组内,假如我们正序遍历,物品就会被多次放入。举个例子,假如w[0]=1,val[0]=15,那么dp[0]=0;

dp[1]=dp[1-w[0]]+val[0]=15,dp[2]=dp[2-w[0]]+15=dp[1]+15=30.在这个例子中,物品0就已经被重复添加了。因为在这个过程中,我们更新所用到的数值已经在前面的更新过程中更新过了。可以理解为我们是将二维数组一行一行的赋值到一维数组内,然而更新时已经用到了前面更新的值,这当然会出现问题。所以我们只能逆序遍历来保证物品只会被使用一次。

接下来我们讨论一下能否将外层循环和内层循环的顺序颠倒,答案也是不行的。假如把顺序颠倒,将背包容量的便利放在外面,就意味着每个包只放入了最大价值的一个物品。这显然也是不符合我们的要求的。所以只能物品在外循环,背包容量在内循环。

具体代码实现如下所示:
 

#include<iostream>
#include<vector> 
using namespace std;
int main()
{int n;int bagweight;scanf("%d%d",&n,&bagweight);vector<int> weight(n+1,0);vector<int> value(n+1,0);for(int i=0;i<n;i++) scanf("%d",&weight[i]);for(int i=0;i<n;i++) scanf("%d",&value[i]);vector<int> dp(bagweight+5,0);for(int i=0;i<n;i++){for(int j=bagweight;j>=weight[i];j--){dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);}}printf("%d",dp[bagweight]);}

416. 分割等和子集

题目链接:416. 分割等和子集 - 力扣(LeetCode)

我一开始其实是想暴力递归的,然后就超时了。所以使用动态规划进行求解。

我们将其带入背包问题,数组的元素值既是重量又是价值,背包容量是sum/2,物品的遍历是0-n,如果dp[sum/2]==sum/2,就说明是可以满足条件的,否则返回false。具体代码实现如下所示:
 

class Solution {
public:bool canPartition(vector<int>& nums) {int sum=0;for(int i=0;i<nums.size();i++) sum+=nums[i];if(sum%2==1) return false;int target=sum/2;vector<int> dp(target+1,0);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;}
};

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com