【代码随想录|动态规划背包问题】

一、背包问题分类

01背包:n种物品,每种物品只有一个

完全背包:n种物品,每种物品有无限个

多重背包:n种物品,每种物品的个数各不相同

二、01背包问题三道题

卡码网46题.携带研究材料(二维背包问题)

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

1.确定dp[i][j]含义: i 个物品,在背包容量为 j 时能得到的 最大价值

dp(n,vector<int>(bagweight+1,0));

这里的dp数组行坐标背包容量是从0开始遍历的,列坐标物品是从第一个物品开始遍历

2.递推公式:dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);

看的时候一头雾水,慢慢整理,我感觉是dp[i][j]每个元素都有两个选择,取或者不取物品i,

不取的话:就是dp[i-1][j],代表我在当前的背包容量下,我还是只装前面一个物品,

取的话:就是dp[i-1][j-weight[i]]+value[i],我先把这个物品的需要的容量先空出来,然后加上i这个物品所能带给我的价值

然后我们进行比较,取还是不取带来的价值大,选大的那个

3.dp数组的初始化

例如

背包为0的时候,什么都装不下,所以dp[i][0]全为0,这个设置数组的时候就可以初始化为0,所以可以不管,然后看我背包容量为几的时候才能装下物品0

从第一个物品需要的容量开始到被背包容量结束初始化为第一个物品的价值

4.遍历顺序:从前向后,从上往下

5.(打印dp数组)

#include<vector>
#include<iostream>
using namespace std;
int main(){
    int n,bagweight;
    cin>>n>>bagweight;//n代表研究材料的种类(物品数量)
                      //bagweight代表小明的行李空间(背包最大重量)
    vector<int>weight(n,0);
    vector<int>value(n,0);
    for(int i=0;i<n;i++){
        cin>>weight[i];//每种研究材料的所占空间(每个物品所占重量)
    }
    for(int i=0;i<n;i++){
        cin>>value[i];//每种研究材料的价值(每个物品的价值)
    }
     vector<vector<int>> dp(n,vector<int>(bagweight+1,0));
    for(int j=weight[0];j<=bagweight;j++){
        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];
            else{
                dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);
            }
        }
    }
    cout<<dp[n-1][bagweight];
    return 0;
}

卡码网46题.携带研究材料(二维背包问题)

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

1.确定dp[i][j]含义:容量为j的背包,所背的物品价值可以最大为dp[j]。

其实就是二维数组转成一维,节省了空间复杂度,但是时间复杂度还是o(n^2)

2.递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

同样的

我感觉是dp[j]每个元素都有两个选择,取或者不取物品i,

不取的话:就是dp[j],因为这个一维数组我还没有更新,还是装的前面一个物品,

取的话:就是dp[j - weight[i]] + value[i],我先把这个物品的需要的容量先空出来,然后加上i这个物品所能带给我的价值

然后我们进行比较,取还是不取带来的价值大,选大的那个

3.dp数组的初始化

因为背包容量为0所背的物品的最大价值就是0,那么dp[0]就应该是0,其他的为了方便就初始化为0就行。

4.遍历顺序:

外层循环从前往后遍历每一个物品,内层循环从后往前,遍历背包的容量

之所以内层是从后往前进行遍历,是因为我从前面往后面遍历的话,我就是已经选了这个物品了,然后我后面在用前面的值的话,就会再重复选这个物品,但是如果我倒序遍历的话,我用到的前一个值就是还没选的值,进行更新就不会选择到前面的物品。

做的时候一直没想明白为什么二维dp数组的i是从1开始,而一维i是从0开始

思考了一下感觉是 

二维 DP 数组i 从 1 开始,是因为第 0 行通常用于初始化,表示没有物品的情况。

一维 DP 数组dp[0] 表示背包容量为 0 时的最大价值,所以初始化时 dp[0] = 0,并且 j 从 0 开始遍历。

二维数组i从1开始是因为0行已经被初始化了,而一维数组从0开始是因为我们初始化也要在这个一维数组里面进行,所以从0开始

5.(打印dp数组)

#include<vector>
#include<iostream>
using namespace std;
int main(){
    int n,bagweight;
    cin>>n>>bagweight;
    vector<int>weight(n,0);
    vector<int>value(n,0);
    for(int i=0;i<n;i++){
        cin>>weight[i];//通过物品的数量一一对应每个物品的重量
    }
    for(int i=0;i<n;i++){//通过物品的数量一一对应每个物品的价值
        cin>>value[i];
    }
     vector<int>dp(bagweight+1,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]);
         }
     }
     
     cout<<dp[bagweight];
    return 0;
}

416.分割等和子集

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

这道题是要看两个子集是否相等,把这个集合所有的元素加起来除以2就是我一个子集需要的达到的价值,思路就是把它看成价值和容量相等的一维背包问题,

我觉得就是把书包容量设置为一个子集的大小,大乱炖,胡乱的取物品在书包里,如果到最后背包价值等于一个子集的价值,那另一个子集肯定也是这个价值(因为如果背包价值不等于一个子集的价值,两个子集相加是sum是固定的,就说明另一个子集要小一点,那两个子集肯定就不相等了)

然后我们就可以着手实现他了,因为把书包容量和价值看成一样的,我们就先遍历物品,选择拿或者不拿,递推公式就为:

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

 其他的思路跟一维背包问题还是一样的

最开始一直没搞懂dp数组的大小怎么设置得那么大都是10001了,后来慢慢理发现

是因为传入的每个数每个数最大为100,传入的数的数量不超过200因为要算每个数总和sum/2为dp的大小,算出来大小为10000,又因为dp从0开始所以为10001

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        vector<int>dp(10001,0);
        int sum=0;
        for(int i=0;i<nums.size();i++){
            sum+=nums[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;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值