代码随想录算法训练营第41天|01背包理论基础(二维)、01背包理论基础(滚动数组,一维)、416. 分割等和子集

本文介绍了01背包问题的基础概念,包括暴力解法的指数时间复杂度,以及使用动态规划进行优化的方法,重点讲解了滚动数组和递推公式的应用,以及如何将问题转化为01背包最大重量问题。
摘要由CSDN通过智能技术生成

01背包理论基础(二维)

题目链接:link
文章讲解:link
视频讲解:link

一、做题感受&第一想法

没什么好想法,之前没写过背包问题。

二、学习文章后收获

1.暴力解法:回溯

每种物品有“取”与“不取”两种状态。可以当作一个组合问题。
时间复杂度就是 o ( 2 n ) o(2^n) o(2n),这里的n表示物品数量。
所以暴力的解法是指数级别的时间复杂度。进而才需要动态规划的解法来进行优化!

2.动态规划

  • 动规五要素分析
    • dp[i][j]的含义: 背包容量为 j 时,从0~ i 号物品中任取,能得到的最大价值
    • 递推公式:dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]]+value[i]);
    • dp初始化:
      • 第一行:当j >= weight[0]时,dp[0][j] = value[0];当j < weight[0]时,dp[0][j] = 0
      • 第一列:j = 0,也就是背包容量为0(装不下任何东西),则能装的最大价值也为0,故第一列全为0。
    • dp遍历顺序:逐行遍历
    • 推算dp(略)
#include<iostream>
#include<vector>

using namespace std;

int main(){
    int m = 0,n = 0;
    cin >> m;
    cin >> n;
    
    vector<int> weight(m);
    vector<int> value(m);
    vector<vector<int>> dp(m,vector<int>(n+1,0)); //注意规格!
    
    for(int i = 0;i < m;i++){
        cin >> weight[i];
        // cout << weight[i] <<endl;
    }
    for(int i = 0;i < m;i++){
        cin >> value[i];
    }
    
    //初始化dp
    for(int i = 0;i <= n;i++){
        if(i < weight[0]) dp[0][i] = 0;
        else if(i >= weight[0]) dp[0][i] = value[0];
    }
    
    //遍历并计算dp
    for(int i = 1;i < m;i++){
        for(int j = 1;j <= n;j++){
            if(j - weight[i] >= 0) dp[i][j] = max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);
            else dp[i][j] = dp[i-1][j];
        }
    }
    
    cout << dp[m-1][n] << endl;
    
    return 0;
}

3.注意点

  • dp数组的规模:如果有m个物品,背包容量为n,则dp数组规模为m*(n+1),申请代码为vector<vector<int>> dp(m,vector<int>(n+1,0))(因为物品从0开始标号到m-1,背包容量从0到n)
  • 递推公式的说明:dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]]+value[i])
    • 不装入物品 i :dp[i-1][j]
    • 装入物品 i :dp[i-1][j-weight[i]]+value[i]
  • 用递推公式时注意判断dp[i-1][j-weight[i]]是否越界(j-weight[i] > 0才行)

01背包理论基础(滚动数组,一维)

题目链接:link
文章讲解:link
视频讲解:link

一、做题感受&第一想法

无。

二、学习文章后收获

1.滚动数组注意点

  • 递推公式:dp[j] = max(dp[j],dp[j-weight[i]]+value[i])
  • 遍历顺序:
    • for循环控制物品 i ,从前往后,物品序号1~(m-1)
    • for循环控制背包容量 j ,从后往前,容量从n到1(或0)
    • 说明:为什么背包容量要从后往前?答:因为如果从前往后,则修改dp过程中相当于把上一层的信息覆盖了,后续dp更新要用到上一层的信息,但是前面的更新很可能已经把原数据覆盖!
#include<iostream>
#include<vector>

using namespace std;

int main(){
    int m,n;
    cin >> m;
    cin >> n;
    
    vector<int> dp(n+1,0);
    vector<int> space(m);
    vector<int> value(m);
    
    for(int i = 0;i < m;i++){
        cin >> space[i];
    }
    for(int i = 0;i < m;i++){
        cin >> value[i];
    }
    
    //初始化
    for(int i = 0;i <= n;i++){
        if(i >= space[0]) dp[i] = value[0];
        else if(i < space[0]) dp[i] = 0;
    }
    
    //生成dp
    for(int i = 1;i < m;i++){ //遍历物品:从前往后
        for(int j = n;j >= 1;j--){ //遍历背包:从后往前,一直到第二个元素
            if(j-space[i] >= 0) 
                dp[j] = max(dp[j],dp[j-space[i]] + value[i]);
        }
    }
    cout << dp[n] << endl;
    
    return 0;
}

416. 分割等和子集

题目链接:link
文章讲解:link
视频讲解:link

一、做题感受&第一想法

1.第一想法:回溯

组合问题+剪枝
但是超出时间限制
代码:(仅记录!!超时了)

class Solution {
public:

    bool canPartition(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        vector<bool> used(nums.size(),false);
        int sum = 0;
        vector<int> path;
        for(int i = 0;i < nums.size();i++){
            sum += nums[i];
        }
        if(sum % 2 == 1) return false;
        sum /= 2;
        bool flag = false;
        backtracking(sum,nums,path,0,0,flag,used);
        return flag;
    }
    void backtracking(int sum,vector<int> nums,vector<int>& path,int curSum, int startIndex,bool &flag,vector<bool> &used){
        if(sum == curSum){
            flag = true;
            return;
        }
        else if(curSum > sum) return; 
        else if(startIndex == nums.size()) return;

        for(int i = startIndex;i < nums.size();i++){
            if(i >= 1 && nums[i] == nums[i-1] && used[i-1] == false) continue; //used[i-1]而不是used[i]!!!
            path.push_back(nums[i]);
            curSum += nums[i];
            used[i] = true;
            backtracking(sum,nums,path,curSum,i + 1,flag,used);
            curSum -= nums[i];
            path.pop_back();
            used[i] = false;
        }
        return;
    }
};

二、学习文章后收获

1.动态规划01背包问题

  • 对比经典01背包问题:本题中的nums数组,既是背包问题中的价值数组value,也是重量数组weight!
  • 先求出nums所有元素之和sum,如果为奇数必然不可划分;如果为偶数则取n = sum / 2作为背包最大容量。
  • 本问题可化为01背包问题:求容量为n的背包可承载的最大重量是多少?即求dp[n]
  • dp[j]:容量为j的背包最多可以装多少重量的东西(物品序号0~i),天然地有dp[j] <= j
  • 如果dp[n] == n,说明找到了一种划分使两子集之和相等,返回true。如果dp[n] < n,则返回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 n = sum/2;
        vector<int> dp(n+1,0); //dp[j]容量为j的背包最多可以装多少重量的东西(物品序号0~i)

        //初始化
        for(int i = 0;i <= n;i++){
            if(nums[0] <= i) dp[i] = nums[0];
            else dp[i] = 0;
        }

        //生成dp
        for(int i = 1;i < nums.size();i++){
            for(int j = n;j >= 1;j--){
                if(j >= nums[i]) dp[j] = max(dp[j],dp[j-nums[i]] + nums[i]);
            }
        }

        if(dp[n] == n) return true;
        else return false;
    }
};

2.本题可抽象为:01背包最大重量问题

经典的01背包问题,是求每个容量下的最大“价值”。
而本题可看作,求每个容量下的最大承载“重量”。

天然地,dp[j] <= j
且本题只需要一个nums数组,既是背包问题中的价值数组value,也是重量数组weight。


  • 20
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值