代码随想录算法训练营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背包每个物品只能用一次)

明天继续加油!

相关推荐
<但凡.3 分钟前
题海拾贝:力扣 138.随机链表的复制
数据结构·算法·leetcode
安大小万20 分钟前
C++ 学习:深入理解 Linux 系统中的冯诺依曼架构
linux·开发语言·c++
田梓燊34 分钟前
图论 八字码
c++·算法·图论
Tanecious.1 小时前
C语言--数据在内存中的存储
c语言·开发语言·算法
去往火星1 小时前
opencv在图片上添加中文汉字(c++以及python)
开发语言·c++·python
Bran_Liu2 小时前
【LeetCode 刷题】栈与队列-队列的应用
数据结构·python·算法·leetcode
kcarly2 小时前
知识图谱都有哪些常见算法
人工智能·算法·知识图谱
CM莫问2 小时前
<论文>用于大语言模型去偏的因果奖励机制
人工智能·深度学习·算法·语言模型·自然语言处理
程序猿零零漆3 小时前
《从入门到精通:蓝桥杯编程大赛知识点全攻略》(五)-数的三次方根、机器人跳跃问题、四平方和
java·算法·蓝桥杯
Zfox_3 小时前
【Linux】进程间关系与守护进程
linux·运维·服务器·c++