代码随想录算法训练营day35

代码随想录算法训练营

---day35

文章目录

  • 代码随想录算法训练营
  • 前言
  • [一、01背包问题 二维](#一、01背包问题 二维)
  • [二、 01背包问题 一维 (滚动数组)](#二、 01背包问题 一维 (滚动数组))
  • [三、416. 分割等和子集](#三、416. 分割等和子集)
  • 总结

前言

今天是算法营的第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数组:

代码如下:

cpp 复制代码
#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数组:

代码如下:

cpp 复制代码
#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数组

代码如下:

cpp 复制代码
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背包每个物品只能用一次)

明天继续加油!

相关推荐
代码小将2 小时前
Leetcode209做题笔记
java·笔记·算法
Musennn3 小时前
leetcode 15.三数之和 思路分析
算法·leetcode·职场和发展
君鼎4 小时前
C++设计模式——单例模式
c++·单例模式·设计模式
刚入门的大一新生5 小时前
C++初阶-string类的模拟实现与改进
开发语言·c++
CM莫问5 小时前
<论文>(微软)避免推荐域外物品:基于LLM的受限生成式推荐
人工智能·算法·大模型·推荐算法·受限生成
小冯的编程学习之路6 小时前
【软件测试】:推荐一些接口与自动化测试学习练习网站(API测试与自动化学习全攻略)
c++·selenium·测试工具·jmeter·自动化·测试用例·postman
康谋自动驾驶6 小时前
康谋分享 | 自动驾驶仿真进入“标准时代”:aiSim全面对接ASAM OpenX
人工智能·科技·算法·机器学习·自动驾驶·汽车
C++ 老炮儿的技术栈7 小时前
什么是函数重载?为什么 C 不支持函数重载,而 C++能支持函数重载?
c语言·开发语言·c++·qt·算法
猪八戒1.07 小时前
C++ 回调函数和Lambda表达式
c++
yychen_java8 小时前
R-tree详解
java·算法·r-tree