代码随想录算法训练营第三十五天|背包问题理论基础、携带研究材料、分割等和子集

背包问题理论基础

1.背包问题概述

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

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

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

2. 01背包

有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

暴力解法

每一件物品其实只有两个状态,取或者不取,所以可以使用回溯法搜索出所有的情况,那么时间复杂度就是O(2^n),这里的n表示物品数量。

所以暴力的解法是指数级别的时间复杂度。进而才需要动态规划的解法来进行优化!

动态规划解法(二维数组)

dp[i][j]的含义:下标为[0,i]之间的物品任取放进容量为j的背包里所能装的最大价值。

递推公式:对于dp[i][j]如果不放物品i所能容纳的最大价值为dp[i-1][j],如果放物品i的最大价值应该等于背包容量减去物品i的容量所能放的最大价值加上物品i的价值:dp[i-1][j-weight[i]]+value[i]。

则取最大值递推公式为:如果当前背包容量装不下物品i则递推公式为dp[i][j]=dp[i-1][j],背包能装下物品i递推公式为dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);

初始化:由递推公式可以看出dp[i][j]是由其上方以及上方左侧的格子推到而来,所以二维数组的第一行和最左边一行需要初始化。最左边一行也就是容量j为0时最大价值为多少那肯定是0,所以dp[i][0]=0,最上面一行表示放入物品0,当容量小于物品0的重量时的格子初始化为0,当容量大于等于物品0重量的格子初始化为物品0的重量。

遍历顺序:两层for循环一层遍历物品,一层遍历背包,顺序可以颠倒,先遍历谁都可以,无非就是一列一列遍历或者一行一行遍历。

携带研究材料 卡码网 46

#include<iostream>
#include<vector>
#include<algorithm>

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<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<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]);
            } 
        }
    }
    cout<<dp[n-1][bagWeight]<<endl;
    
    return 0;
}

一维数组(滚动数组)

使用二维数组填表时,我们发现每一个表格只与它上一行的信息有关,那么我们可以考虑一行一行填表,将二维数组简化为一维数组,for循环遍历物品,第一次填入二维数组的第一行,第二次由第一行的信息推出第二行的信息并覆盖一维数组,以此类推得到二维数组的最后一行。

dp[j]含义:容量为j的背包所能装的最大价值为dp[j]。

递推公式:如果不放物品i则dp[j]=dp[j],放物品i则dp[j]=dp[j-weight[i]]+value[i]。

则递推公式为dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);

初始化:首先考虑对dp[0]初始化,dp[0]表示容量为0的背包所能装的最大价值那肯定是0,所以dp[0]=0。数组其它位置应该初始化为什么呢?根据递推公式可以看出,如果放物品i我们是从dp[j]和dp[j-weight[i]]+value[i]取最大值,所以dp[j]就不能初始化为一个比较大的值否则会把正确结果覆盖,所以初始化dp[j]=0(j>0)。

遍历顺序

首先遍历背包时应该倒叙遍历,如果正序遍历的话,假设此时遍历到j,前面已经是对应二维数组下一行的信息了,而j后面还是对应二维数组上一行的信息,我们继续向后遍历此时就相当于在二维数组中使用同一行前面的格子的信息来推导后面格子的信息(因为dp[j]和dp[j-weight[i]]有关)这样肯定是不对的。所以必须使用倒叙遍历。

为什么必须先遍历物品再遍历背包呢?同样的道理,一维数组应该由二维数组转化一行一行的去填,如果先遍历背包那么一维数组第一个数直接就变成最后一行的数了,再继续向后推导肯定不对的(我是这么理解的不知道对不对)。

携带研究材料一维数组解法

#include<iostream>
#include<vector>
#include<algorithm>

using namespace std;

int main(){
    int M,N;
    cin>>M>>N;
    
    vector<int> costs(M);
    vector<int> values(M);
    
    for(int i=0;i<M;i++){
        cin>>costs[i];
    }
    for(int i=0;i<M;i++){
        cin>>values[i];
    }
    vector<int> dp(N+1,0);
    for(int i=0;i<M;i++){
        for(int j=N;j>=costs[i];j--){
            dp[j]=max(dp[j],dp[j-costs[i]]+values[i]);
        }
    }
    cout<<dp[N]<<endl;
    return 0;
}

分割等和子集 leetcode 416

取数组元素如果可以等于数组元素总和的一半,那么这个问题就解决了,而且使用的元素不能重复,可以把本题转化为一个01背包问题。

dp[j]的含义:容量为j的背包可以容纳的最大价值,也就是容量为j的数组容纳元素最大总和。

如何判断背包装满了呢?就是dp[target]=target,其中target=数组总和/2。

递推公式:在本题中重量和价值是相同的都是数组中元素的数值,则

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

初始化:dp[j]=0。

遍历顺序:就是01背包一维数组的遍历顺序,先遍历物品也就是数组中元素,之后倒叙遍历背包也就是target。

求和也可以使用库函数,accumulate(nums.begin(),nums.end(),0);其中0表示从0开始累加。

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        // 总和不会大于20000,背包最大只需要其中一半,所以10001大小就可以了
        vector<int> dp(10001,0);
        int target=0;
        for(int i=0;i<nums.size();i++){
            target+=nums[i];
        }
        if(target%2==1) return  false;
        target=target/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;
        return false;
    }
};
  • 24
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值