代码随想录算法训练营第四十二天| 二维背包、一维背包、LeetCode 416.分割等和子集

一、二维背包

文章讲解/视频讲解:https://programmercarl.com/%E8%83%8C%E5%8C%85%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%8001%E8%83%8C%E5%8C%85-1.html

状态:已解决

1.背包问题介绍

        背包问题实则是一类问题的集合,有好多不同小类型,但本质还是一样的,基本组成都是:一个容量固定的背包、若干件物品(拥有体积、价值、数量三种属性),根据物品的数量的限制,可以将背包问题划分为以下几类:

        背包问题是动态规划的经典问题,也是难点所在,一般来说,学会01背包和完全背包就够做题了,再往上就是竞赛级别的了。而完全背包实则也是在01背包的基础上进行的变化升级,因此,01背包是重中之重,我们必须掌握。

2.01背包问题

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

         这种取或不取的问题,我们自然容易想到用回溯法遍历所有情况暴力去做,但这种方法时间复杂度太高(指数级),在有更优化的解法时不建议使用。

        这里说的更优化的解法实则就是动态规划。具体怎么做我们利用动规五部曲来分析。

(1)确定dp数组以及下标含义:

        我们知道动规中dp数组是用于表示状态的一个数组,那我们这道题有哪些状态呢?首先,我们需要一个状态说明此刻该抉择选取哪个物品了,另外我们需要一个状态来说明此时背包的容量。那么,我们就可以定义一个二维数组dp。dp[i][j] 表示从下标为[0-i]的物品里任意取(0 <= k <=i,可能取了物品k也可能没取物品k),放到容量为j的背包,价值总和最大是多少。画出dp数组如图:

(2)确定递推公式:

        我们知道了dp[i][j]的含义:抉择第i个物品时,背包重量为j,那么结合逻辑,我们就可以得出状态dp[i][j]的来源:

  • 不放物品i:由dp[i - 1][j]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]就是dp[i - 1][j]。(其实就是当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以背包内的价值依然和前面相同。)
  • 放物品i:由dp[i - 1][j - weight[i]]推出,dp[i - 1][j - weight[i]] 为背包容量为j - weight[i]的时候不放物品i的最大价值,那么在容量为 j 时可以选择多放入物品 i ,dp[i - 1][j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值。

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

(3)dp数组初始化:

        首先从dp[i][j]的定义出发,如果背包容量j为0的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为0(放不下任何东西)。如图:

         状态转移方程 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 可以看出i 是由 i-1 推导出来,那么i为0的时候就一定要初始化。dp[0][j],即:i为0,存放编号0的物品的时候,各个容量的背包所能存放的最大价值。那么很明显当 j < weight[0]的时候,dp[0][j] 应该是 0,因为背包容量比编号0的物品重量还小。当j >= weight[0]时,dp[0][j] 应该是value[0],因为背包容量放足够放编号0物品。代码初始化如下:

        其余下标由于dp[i][j]都是由左上角或者上面推导过来的,因此初始化就无所谓,反正都会被覆盖,为了方便,统一初始化为0。

(4)遍历顺序:

        我们知道dp是一个二维数组,也就是有两个维度:物品和背包容量,那么我们在进行循环的时候哪个维度放外层哪个维度放内层呢?我们根据递推公式可以知道一个元素是从左上和上面推出来的,也就是说,我们将物品作为外层,背包容量作为内层的话,那么从小到大遍历dp数组就是一排一排填满的,如果将背包容量作为外层,物品作为内层的话,那么dp数组就是一列一列的填满的。二者都可以顺利计算完整个dp数组(只要每排或每列是从小到大遍历的,那么我们做下一排或者下一列时,无论如何都能得到上面一格或者左上一格的元素)。

(5)举例推导dp数组:

        按现在的逻辑得到dp数组如下:

        最终结果就是dp[2][4]。 

3.具体实现 

        卡码网46题就是一个经典的01背包问题,我们根据上述分析可以给出代码:

#include<iostream>
#include<vector>
using namespace std;
int main(void){
    int m,n;
    cin>>m>>n;
    vector<int> weight(m,0);
    vector<int> value(m,0);
    vector<vector<int>> dp(m,vector<int>(n+1,0));
    for(int i=0;i<m;i++){
        cin>>weight[i];
    }
    for(int i=0;i<m;i++){
        cin>>value[i];
    }
    for(int i=weight[0];i<=n;i++){
        dp[0][i] = value[0];
    }
    for(int i=1;i<m;i++){
        for(int j=0;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];
        }
    }
//    for(int i=1;i<m;i++){
//        for(int j=0;j<=n;j++){
//            cout<<dp[i][j]<<" ";
//        }
//        cout<<endl;
//    }
    cout<<dp[m-1][n];
}

二、一维背包 

文章讲解/视频讲解:https://programmercarl.com/%E8%83%8C%E5%8C%85%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%8001%E8%83%8C%E5%8C%85-2.html

状态:已解决

1.滚动数组 

        一维背包实则还是01背包问题,只是我们将状态数组从二维降到了一维。依据是什么呢?我们根据二维背包的分析可以得知二维dp数组的每一层的每一个格子(除初始化的最左侧和最上方)实则都是从上一层的左上角格子和正上方格子推导出来的,也就是说,每一层的值只跟上一层的值有关,跟这层的值没有关系。也就是说,我们只需要保存上一层的状态,然后推导出这一层的状态后,舍弃上一层状态,更新为新推导出的这一层状态,由此再去推导下一层。即,整个二维数组被压缩成一行(层),然后不断地向下滚动着更新,故称之为滚动数组。由于数组降维,从两个状态变为了一个状态,故这种优化方法也被称为状态压缩。

(1)确定dp数组及下标含义:

在一维dp数组中,dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]。

(2)一维dp数组的递推公式:       

  我们知道二维dp数组的递推公式是:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]),i是物品标号也是层数标号,当层数被压缩为一层时,我们知道当前一维数组实际就是推导完的上层状态,那么原先上层的dp[i - 1][j], dp[i - 1][j - weight[i]],实际就是现在的dp[j]、dp[j-weight[i]]。故递推公式为:

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

         含义:dp[j - weight[i]] + value[i] 表示 容量为 j - 物品i重量 的背包 加上 物品i的价值。(也就是容量为j的背包,放入物品i了之后的价值即:dp[j])

        此时dp[j]有两个选择,一个是取自己dp[j] 相当于 二维dp数组中的dp[i-1][j],即不放物品i,一个是取dp[j - weight[i]] + value[i],即放物品i。

(3)dp数组的初始化:
        dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j],那么dp[0]就应该取0,因为容量为0的书包装不下任何东西,故所背的物品的最大价值就是0。

        那么dp数组除了下标0的位置,初始为0,其他下标应该初始化多少呢?看一下递归公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); dp数组在推导的时候一定是取价值最大的数,如果题目给的价值都是正整数那么非0下标都初始化为0就可以了。这样才能让dp数组在递归公式的过程中取的最大的价值,而不是被初始值覆盖了

(4)一维dp数组的遍历顺序:       

        假如现在外层是依次遍历物品。那么,内层的背包容量也还是从小到大来遍历吗?漏!!!我们知道,内层循环现在是背包容量,由于现在的dp[ j ]是由dp[j]和dp[j-weight[i]]决定的,那么我们推导dp[j]时,本身需要的是上一层在 j 和 j-weight[i]时的值,而如果每次对于物品i,都从小到达遍历容量的话,那么在算这层的 j容量 之前时,它前面的容量值((比如j - weight[i]))就已经被更新成这层的推算值,而不是上层的推算值了。因此,我们只会反复根据第一层物品的价值和重量进行推导,而不是这层物品的相关值。

        例:物品0的重量weight[0] = 1,价值value[0] = 15。如果正序遍历

        dp[1] = dp[1 - weight[0]] + value[0] = 15

        dp[2] = dp[2 - weight[0]] + value[0] = 30

此时dp[2]就已经是30了,意味着物品0,被放入了两次,所以不能正序遍历。

        为什么倒序遍历,就可以保证物品只放入一次呢?因为倒序从当层的末尾容量开始推导,那么只动了j容量后面的值,j前面的值仍然为上层的推算值,由此推得的dp[j] 也就是由上层推算值推导出来的正确的dp[j]。倒序就是先算dp[2]dp[2] = dp[2 - weight[0]] + value[0] = 15 (dp数组已经都初始化为0)dp[1] = dp[1 - weight[0]] + value[0] = 15。所以从后往前循环,每次取得状态不会和之前取得状态重合,这样每种物品就只取一次了。

       再来看看两个嵌套for循环的顺序,代码中是先遍历物品嵌套遍历背包容量,那可不可以先遍历背包容量嵌套遍历物品呢?不可以!因为一维dp的写法,背包容量一定是要倒序遍历(原因上面已经讲了),如果遍历背包容量放在上一层,那么每个dp[j]就只会放入一个物品,即:背包里只放入了一个物品。

倒序遍历的原因是,本质上还是一个对二维数组的遍历,并且右下角的值依赖上一层左上角的值,因此需要保证左边的值仍然是上一层的,从右向左覆盖。

(5)举例推导dp数组:

2.代码实现 

        还是以卡码网46题为例。

#include<iostream>
#include<vector>
using namespace std;
int main(void){
    int m,n;
    cin>>m>>n;
    vector<int> weight(m,0);
    vector<int> value(m,0);
    vector<int> dp(n+1,0);
    for(int i=0;i<m;i++){
        cin>>weight[i];
    }
    for(int i=0;i<m;i++){
        cin>>value[i];
    }
    for(int i=0;i<m;i++){
        for(int j=n;j>=0;j--){
            if(j-weight[i]>=0)
            	dp[j] = max(dp[j],dp[j-weight[i]]+value[i]);
            else
            	dp[j] = dp[j];
        }
    }
//    for(int i=1;i<m;i++){
//        for(int j=0;j<=n;j++){
//            cout<<dp[i][j]<<" ";
//        }
//        cout<<endl;
//    }
    cout<<dp[n];
}

三、LeetCode 416.分割等和子集

题目链接/文章讲解/视频讲解:https://programmercarl.com/0416.%E5%88%86%E5%89%B2%E7%AD%89%E5%92%8C%E5%AD%90%E9%9B%86.html

状态:已解决

1.思路 

        这道题不难,关键在于如何将问题转换为01背包问题。题眼:将这个数组分割成两个子集,使得两个子集的元素和相等。我们知道,给定了数组,那么数组的和sum就是确定的,也就是说,我们现在的目标就是找一个集合使得集合元素之和等于sum/2,那么剩余元素构成的集合的和也就为sum/2了。现假设数组元素个数为m,那么套用到背包模型中,就是将m个物品(重量、价值均为nums[i]),装到容量为sum/2的背包中去,看是否能够装满背包。

        那么怎么判断是否能够装满背包呢?看背包最多能装的总价值是否刚好等于sum/2,即dp[target] == target。理由:

        (1)dp[target] > target 的情况不可能出现,因为现在一个物品的价值等于这个物品的重量,装满一个背包最多价值等于重量,不可能出现最终价值超过该背包容量的情况。

        (2)dp[target] < target:说明尽量装背包装不到tagret,也就是装不满背包,故不可能凑到某个集合的元素之和等于sum/2。

2.代码实现

        直接在上面的代码上做修改就行:m = num.size(),n = sum/2,weight[i] = nums[i],value[i] = nums[i]。

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = 0;
        for(int i=0;i<nums.size();i++){
            sum += nums[i];
        }
        int m = nums.size(),n = sum/2;
        if(sum % 2 != 0) return false;//此处可剪枝
        vector<int> dp(n+1,0);
        for(int i=0;i<m;i++){
            for(int j=n;j>=0;j--){
                if(j-nums[i]>=0)
                    dp[j] = max(dp[j],dp[j-nums[i]]+nums[i]);
                else
                    dp[j] = dp[j];
            }
        }
        if(dp[n] == n) return true;
        else return false;
    }
};

时间复杂度:O(n^2)

空间复杂度:O(n)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/549561.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

单位优秀信息宣传员告诉你向媒体投稿你不知道的好方法

作为基层社区信息宣传工作队伍中的一员,我刚开始接手单位的信息宣传投稿任务时,真的是一片茫然。没有任何媒体编辑的熟人朋友,我只能硬着头皮,一家家地去联系媒体,沟通投稿的事宜。这个过程真的是既费事又费力,每次投稿都像是在茫茫大海中寻找那一丝被认可的机会。 因为媒体对稿…

VLAN Mapping原理描述

基本原理 路由器收到带Tag的数据报文后&#xff0c;根据配置的VLAN Mapping方式&#xff0c;决定替换外层Tag中的VLAN ID或优先级&#xff1b;然后进入MAC地址学习阶段&#xff0c;根据源MAC地址映射后的VLAN ID刷新MAC地址表项&#xff1b;根据目的MAC映射后VLAN ID查找MAC地…

T细胞耗竭

目录 T Cell Exhaustion T 细胞衰竭路径上的细胞和分子路标 研究起源 介绍 T 细胞耗竭的发生路径 耗尽的T细胞亚群的解剖分离和迁移 持续TCR刺激的收益递减 通过共调节受体进行发育微调 细胞因子介导的耗尽T细胞亚群的特异性 T细胞耗竭和表观遗传 T Cell Exhaustion…

面试问答之转账功能测试点详解

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

快速排序题目SelectK问题

力扣75.颜色分类 给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums &#xff0c;原地对它们进行排序&#xff0c;使得相同颜色的元素相邻&#xff0c;并按照红色、白色、蓝色顺序排列。 我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。 必须在不使用库内置的 sor…

数据库(mysql)-连接嵌套查询-2

子查询 MySQL中的子查询&#xff08;Subquery&#xff09;是嵌套在其他SQL查询中的查询。子查询可以出现在SELECT、FROM或WHERE子句中&#xff0c;并用于返回将被用于外部查询的数据。子查询的结果可以是一个单一的值、一行、一列或多行多列的数据集。 单行单列查询 实例 #查…

一款挺不错网站维护页面HTML源码

一款挺不错网站维护页面源码&#xff0c;单HTML不需要数据库&#xff0c;上传到你的虚拟机就可以用做维护页面还不错&#xff0c;用处多。。 源码下载 一款挺不错网站维护页面源码

【智能算法】鱼鹰优化算法(OOA)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献 1.背景 2023年&#xff0c;M Dehghani等人受到自然界鱼鹰狩猎行为启发&#xff0c;提出了鱼鹰优化算法&#xff08;Osprey Optimization Algorithm, OOA&#xff09;。 2.算法原理 2.1算法思想 OOA基本灵…

网络爬虫:定义、应用及法律道德考量

网络爬虫技术在当今数据驱动的世界中发挥着重要作用。本文将从网络爬虫的定义和主要功能&#xff0c;其在业界的应用实例&#xff0c;以及涉及的法律和道德问题三个方面进行深入探讨。 1. 爬虫的定义和主要功能 网络爬虫&#xff0c;也称为网页爬虫或蜘蛛&#xff0c;是一种…

苹果与印度深入洽谈,开启新业务 | 百能云芯

印度经济时报&#xff08;ET&#xff09;引述知情人士报导&#xff0c;苹果&#xff08;Apple&#xff09;正和 Murugappa 集团和塔塔集团旗下的Titan公司深入洽谈&#xff0c;将由这两家印度业者组装、甚至生产 iPhone 所用的相机模组。 苹果正将iPhone供应链向中国大陆以外地…

SpringBoot学习(三)数据访问、基础特性、核心原理

文章目录 数据访问示例自动配置原理jdbc场景自动配置数据源等基本信息MyBatisAutoConfiguration配置MyBatis整合流程 基础特性SpringApplication自定义banner自定义SpringApplicationFluentBuilder API Profiles使用指定环境环境激活环境包含 Profiles配置文件 外部化配置配置优…

JVM结构化体系

目录 目录 1.JVM 简介 1.1. 如何理解 JVM 呢&#xff1f; 1.2. 市场主流 JVM 分析&#xff1f; 1.3. 为什么要学习 JVM&#xff1f; 1.4. 字节码底层是如何执行呢&#xff1f; 如何理解 JIT 呢&#xff1f; 为什么 JVM 中解释执行与编译执行的并存&#xff08;混合模式&…

新手教程 | 2024年最新Vmware17安装教程及许可证(详细图文)

目录 前言&#xff1a; 一、VMware Workstation 17 Pro 简介 二、下载安装&#xff08;以Windows为例&#xff09; 三、许可证 四、检查是否安装成功 前言&#xff1a; 重新装电脑后&#xff0c;安装虚拟机 一、VMware Workstation 17 Pro 简介 VMware Workstation 17 …

【JavaWeb】Day46.Mybatis——入门

JDBC介绍 通过Mybatis可以很方便的进行数据库的访问操作。其实java语言操作数据库&#xff0c;只能通过一种方式&#xff1a;使用sun公司提供的 JDBC 规范。Mybatis框架&#xff0c;就是对原始的JDBC程序的封装。 JDBC&#xff1a; ( Java DataBase Connectivity )&#xff0c…

元类的执行

class MetaB(type):def __new__(cls, name, bases, attrs):print(f"使用元类 {cls.__name__} 创建{name}类 ")return super().__new__(cls, name, bases, attrs)class A(metaclassMetaB):passclass C(A):pass元类MetaB的__new__方法应该只会在创建类A时被调用一次, 因…

YoloV9实战:从Labelme到训练、验证、测试、模块解析

模型实战 训练COCO数据集 本次使用2017版本的COCO数据集作为例子&#xff0c;演示如何使用YoloV8训练和预测。 下载数据集 Images: 2017 Train images [118K/18GB] &#xff1a;http://images.cocodataset.org/zips/train2017.zip2017 Val images [5K/1GB]&#xff1a;htt…

Python-VBA函数之旅-compile函数

目录 1、 compile函数&#xff1a; 1-1、Python&#xff1a; 1-2、VBA&#xff1a; 2、相关文章&#xff1a; 个人主页&#xff1a;https://blog.csdn.net/ygb_1024?spm1010.2135.3001.5421 compile函数在Python中有多个实际应用场景&#xff0c;它主要用于将字符串形式的…

【C++】类和对象③(类的默认成员函数:拷贝构造函数 | 赋值运算符重载)

&#x1f525;个人主页&#xff1a;Forcible Bug Maker &#x1f525;专栏&#xff1a;C 目录 前言 拷贝构造函数 概念 拷贝构造函数的特性及用法 赋值运算符重载 运算符重载 赋值运算符重载 结语 前言 本篇主要内容&#xff1a;类的6个默认成员函数中的拷贝构造函数…

matlab使用教程(45)—二维曲线图绘制进阶

1.绘制双y轴曲线图 此示例说明如何使用两个不同的 y 轴合并线图和条形图。此外&#xff0c;还演示如何自定义线条和条形。 使用 yyaxis 创建包含两个 y 轴的图表。图形函数以图表的活动侧为目标。使用 yyaxis 控制活动侧。使 用左侧的 y 轴绘制条形图。使用右侧的 y 轴绘制线…

PLC扩展更自由,钡铼IOy系列模块实现DI/DO/AI/AO任意组合

随着工业自动化的不断发展&#xff0c;PLC&#xff08;可编程逻辑控制器&#xff09;作为工业控制领域的核心设备&#xff0c;扮演着至关重要的角色。而钡铼IOy系列模块作为PLC的重要扩展设备&#xff0c;不仅实现了DI&#xff08;数字输入&#xff09;、DO&#xff08;数字输出…
最新文章