代码随想录算法训练营day35

代码随想录算法训练营

—day35


前言

今天是算法营的第35天,希望自己能够坚持下来!
今天开始是背包问题了,今日任务:
● 01背包问题 二维
● 01背包问题 一维
● 416. 分割等和子集

先上个图,背包问题的分类:
在这里插入图片描述


一、01背包问题 二维

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

01背包问题:
有n个物品和一个最多能装重量为w的背包,每个物品的价值和重量不同,问将哪些物品装入背包里物品价值总和最大。

因为有物品 和 背包容量两个维度,所以dp数组需要用二维,i 来表示物品、j表示背包容量。
在这里插入图片描述

思路:

  1. dp[i][j]的定义为:表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。

  2. 递归公式:对于物品i,在容量为j的时候都有放和不放物品i两种情况,那么dp[i][j]就是取放和不放两种情况的最大值(价值总和最大)
    ·不放:就是只考虑i-1之前的物品dp[i-1][j]
    ·放:则是先腾出物品i的容量,再加上物品i的价值dp[i-1][j - weight[i]] + value[i]
    那么dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

  3. 初始化:
    ·首先,如果背包容量j为0的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为0。
    ·然后根据递推公式可以看出i 是由 i-1 推导出来,那么i为0的时候就一定要初始化。
    当j >= weight[0]时,dp[0][j] 应该是value[0],因为背包容量放足够放编号0物品。
    在这里插入图片描述

  4. 遍历顺序:先遍历物品还是先遍历背包重量都可以,但先遍历物品更好理解。

  5. 举例推导dp数组:
    在这里插入图片描述

代码如下:

#include<iostream>
#include<vector>
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 j = 0; j < n; j++) {
        cin >> value[j];
    }
 
    //i是研究材料,j是行李箱空间
    //dp[i][j]含义:在j的空间里,任意放入i和i一下的研究材料所得到的最大价值
    //对于研究材料,只有放入和不放入两种状态
    //所以对于d[i][j]来说,就是取放入材料i和不放入材料i,这两种情况取价值最大的
    //不放材料i: d[i-1][j], 放材料i:d[i-1][j-weight[i]] + value[i]
    vector<vector<int>>dp (weight.size(), vector<int>(bagweight+1, 0));
 
    //初始化dp,遍历背包空间,记录,也对能装下材料0的j对应的dp[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 = 1; j <= bagweight; j++) {
            //如果当前j大小的空间装不下物品i的话,那么继承dp[i-1][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[" << i << "][" << j << "]:" << dp[i][j] << endl;
        }
    }
     
 
     
    cout << dp[n-1][bagweight] << endl;
    return 0;
 
}

关于为什么可以调换遍历顺序:
先遍历物品,再遍历背包的过程如图所示:
在这里插入图片描述
先遍历背包,再遍历物品呢,如图:
在这里插入图片描述
不管是先遍历物品还是先遍历背包,dp[i][j]所需要的数据就是左上角,都是可以推导出来的。


二、 01背包问题 一维 (滚动数组)

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

在使用二维数组的时候,递推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
而滚动数组原理其实就是将dp[i-1]层的信息拷贝到dp[i]层来,也就是只用一层来记录信息,然后就可以把dp[i]维度去掉,只剩下
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

思路:

  1. dp[j]的定义为:容量为j的背包,所背的物品价值可以最大为dp[j]。。
  2. 递归公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
  3. 初始化:因为递推的时候是求的最大值,且只用到了j, 所以初始化成0就可以了。
  4. 遍历顺序:一维dp数组需要先物品,再遍历背包,且背包是倒序遍历的,因为正序遍历的话物品会重复放入。(如果是完全背包的话就是正序遍历)
  5. 举例推导dp数组:
    在这里插入图片描述

代码如下:

#include <iostream>
#include <vector>
using namespace std;
 
int main() {
    int n, bagweight;
    cin >> n >> bagweight;
     
    vector<int>weight (n);
    vector<int>value (n);
     
    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++) {
        //后序遍历空间,只遍历能装下当前物品i的空间
        for (int j = bagweight; j >= weight[i]; j--) {
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
    }
     
    cout << dp[bagweight] << endl;
    return 0;
}

三、416. 分割等和子集

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

思路:
问题可以转化成,找到一个子集等于数组总和sum的一半。
也就是背包容量是sum/2,找出是否有加起来是sum/2的物品。

本题我用一维dp数组解题。

  1. dp[j]的定义为:容量为j,子集加起来总和为dp[j],也就是最后要求dp[target] == target
  2. 递归公式:01背包的递推公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    本题物品的重量就是价值,所以dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
  3. 初始化:从dp[j]的定义来看,首先dp[0]一定是0。而题目说了价值都是正整数,那么其他也初始化成0就可以了。如果价值有负数出现,那么需要初始化成负无穷。这样才能让dp数组在递推的过程中取得最大的价值,而不是被初始值覆盖了。
  4. 遍历顺序:一维dp数组的话,需要先遍历物品,再遍历背包,且背包需要后序遍历。
  5. 举例推导dp数组
    在这里插入图片描述

代码如下:

class Solution {
public:
    //动态规划,转化成背包问题
    //只需要要求集合的总和sum/2,找出是否有和为sum/2的子集
    //相当于用sum/2的背包去装sum/2的东西
    //一维背包问题递推公式:dp[j] = max(dp[j], dp[j-weight[i]] + value[i])
    bool canPartition(vector<int>& nums) {
        int sum = 0;
        for (int num:nums) {
            sum += num;
        }

        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;
        return false;
    }
};

总结

01背包:n个物品,w空间的背包,每个物品重量不同,价值不同,求背包装最多的价值是多少。
二维dp数组:

  1. dp[i][j] = max(dp[i-1][j], dp[i-1][j - weight[i]] + vlaue[i])
  2. 需要初始化最上层和最左层
  3. 先遍历背包还是物品都可以,因为两种方式都可以得到左上角的数据,先遍历物品比较好理解。

一维dp数组:

  1. dp[j] = max(dp[j], dp[j- weight[i]] + value[i]); 就是二维中去掉i维度,理解成将i-1层的数据拷贝到i层
  2. 因为不涉及到i,所以初始化成0就可以了(如果价值有负数,除了dp[0],需要初始化成负无穷)
  3. 只能先遍历物品再遍历背包,且背包需要后续遍历(正序的话会重复放入物品,01背包每个物品只能用一次)

明天继续加油!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值