Day 42 0-1背包理论基础 416. 分割等和子集

news2024/11/20 14:31:57

01背包理论基础

先了解背包问题的区别和分类:

​ 由于所有的问题的原理都可以转化为01背包;通过纯01背包问题,把01背包原理讲清楚;

01背包

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

​ 先考虑暴力解法:

​ 每一件物品其实只有两个状态,取或者不取,所以可以使用回溯法搜索出所有的情况,那么时间复杂度就是 o ( 2 n ) o(2^n) o(2n),这里的n表示物品数量;

​ 举例说明:

​ 背包最大重量为4。

​ 物品为:

重量价值
物品0115
物品1320
物品2430

​ 问背包能背的物品最大价值是多少?

二维dp数组实现

​ 依然动规五部曲分析一波。

​ 1.确定dp数组以及下标的含义:

​ 对于01背包问题,有两种写法, 这里先使用二维数组,即dp[i][j] 表示从下标为[0-i]的物品类里选择(此题是因为每个目标只有一个的01背包,所以不用考虑这个类,涉及到多个的问题时,物品类的这个“类”就很重要了),放进容量为j的背包,其最终价值总和最大是多少;只看这个二维数组的定义,一定会很懵,看下面这个图:

动态规划-背包问题1

要时刻记着这个dp数组的含义,下面的一些步骤都围绕这dp数组的含义进行的

​ 如果哪里看懵了,就来回顾一下i代表什么,j又代表什么,dp[i][j]又代表什么;

​ 2.确定递推公式:

​ 考虑dp[i][j]的含义:从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少;

​ 可以从两个方向推出来dp[i][j]:

已经放了前0到i - 1个物品,不放物品i:由dp[i - 1][j]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]就是dp[i - 1][j]。(其实就是当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以背包内的价值依然和前面相同)

已经放了前0到i - 1个物品,再放物品i:由dp[i - 1][j - weight[i]]推出,dp[i - 1][j - weight[i]] 为背包容量为j - weight[i]的时候不放物品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数组的定义吻合,否则到递推公式的时候就会越来越乱

​ 首先从dp[i][j]的定义出发,如果背包容量j为0的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为0。

​ 如图:

​ 所以第一列必然是全为0,因为根本放不进东西进去;

​ 再看递推公式:

​ dp[i][j] = max{dp[i - 1][j], dp[i - 1][j - weight[j]] +value[i]};

​ 可以看出,dp[i][j]要么是由正上方,要么是由左上方推出来;

​ 分析第一行:

​ dp[0][j],即:i为0,存放编号0的物品的时候,各个容量的背包所能存放的最大价值。

​ 那么很明显当 j < weight[0]的时候,dp[0][j] 应该是 0,因为背包容量比编号0的物品重量还小。

​ 当j >= weight[0]时,dp[0][j] 应该是value[0],因为背包容量放足够放编号0物品。

​ 初始化代码即如下所示:

	for (int j = 0 ; j < weight[0]; j++) {  
        // 当然这一步,如果把dp数组预先初始化为0了,这一步就可以省略,但很多同学应该没有想清楚这一点。
    	dp[0][j] = 0;
	}
	// 正序遍历
	for (int j = weight[0]; j <= maxweight; j++) {
    	dp[0][j] = value[0];
	}

​ 如本例,初始化就应该如下:

​ 至于其他部分的初始化,由于dp[i][j]的所有其他部分都可以由第一排和第一列退出,所以数组整体无论怎么初始化都行,只要是一个符合范围的合法值即可;为了代码简洁性,此处统一初始化为0;

​ 所以最终初始化代码如下:

	vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));//这一步其实已经包含了第一列的初始化
	for (int j = weight[0]; j <= bagweight; j++) {
    	dp[0][j] = value[0];
	}

​ 4.确定遍历顺序

​ 从上述条件可以看出,遍历维度有两个维度:

​ 物品和背包重量;

​ 那么就是一个考虑遍历先后顺序的问题;

​ 假设先遍历物品,再遍历背包重量

	// weight数组的大小 就是物品个数
	for(int i = 1; i < weight.size(); i++) { // 遍历物品
    	for(int j = 0; j <= bagweight; 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]);
    	}
	}

​ 按照上述代码,实现如下:

先遍历背包,再遍历物品(注意仅针对二维dp数组01背包的情况实用):

	// weight数组的大小 就是物品个数
	for(int j = 0; j <= bagweight; j++) { // 遍历背包容量
    	for(int i = 1; i < weight.size(); i++) { // 遍历物品
        	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]);
    	}
	}

​ 实现如下:

​ 5.打印dp数组

​ 举例推导dp数组

#include<iostream>
#include<vector>
using namespace std;

int n, bagspace;
void test() {
	vector<int> weight(n, 0);
	vector<int> value(n, 0);
    cout << "Please enter the weight array(size is n):" << endl;
    for (int i = 0; i < n; ++i) {
        cin >> weight[i];
    }
    cout << "Please enter the value array(size is n):" << endl;
    for (int j = 0; j < n; ++j) {
        cin >> value[j];
    }
    // dp数组, dp[i][j]代表行李箱空间为j的情况下,从下标为[0, i]的物品里面任意取,能达到的最大价值
    vector<vector<int>> dp(weight.size(), vector<int>(bagspace + 1, 0));

    //初始化,由于已经初始化了dp[i][0](第一列全为0)
    //那么接下来只需要初始化第一行
    //由于当j<bagsapce的时候dp[0][j]=0,所以j可以从weight[0]开取
    for (int j = weight[0]; j < bagspace; j++) {
        dp[0][j] = value[0];
    }

    for (int i = 1; i < weight.size(); i++) {
        for (int j = 0; j <= bagspace; j++) {
            // 如果装不下这个物品,那么就继承dp[i - 1][j]的值
            if (j < weight[i]) dp[i][j] = dp[i - 1][j];
            // 如果能装下,就将值更新为 不装这个物品的最大值 和 装这个物品的最大值 中的 最大值
            // 装这个物品的最大值由容量为j - weight[i]的包任意放入序号为[0, i - 1]的最大值 + 该物品的价值构成
            else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
        }
    }
    cout << dp[weight.size() - 1][bagspace] << endl;
}
int main() {
    cout << "Please enter the numbers of objects and space of the bag:" << endl;
    while (cin >> n >> bagspace) {
        test();
    }
    return 0;
}

一维dp数组实现(滚动数组)

​ 由于dp数组的状态其实是可以压缩的,由二维数组的递推公式:

​ dp[i][j] = max{dp[i - 1][j] , dp[i][j - weight[i]] +value[i]};

​ 考虑将dp[i-1]这层拷贝到dp[i]这一层上,其实只用一维数组就可以实现了;

​ 再次回顾dp[i][j] 的含义

​ i是物品,j是背包容量,dp[i][j]是任取第[0, i ]个物品,放进容量为j的背包能达到的价值最大值;

​ 动态规划五部曲:

​ 1.确定dp数组含义:

​ dp[j]即背包容量为j的背包,能达到的价值最大值dp[j];

​ 2.dp数组的递推公式:
​ dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); 其实很好理解,就是把i的维度去掉了;

​ 3.dp数组的初始化:

​ 显然,dp[0] = 0;由递推公式,可以知道dp[j]是有一个比较取最大值的过程,所以为了避免获取值被初始值覆盖,此时取INT_32MIN是最合适的,这里假设背包容量均大于0,可以全部初始化为零;

​ 4.dp数组遍历顺序:

注意,以为和二维最大的区别就在遍历顺序上

	for(int i = 0; i < weight.size(); i++) { // 遍历物品
    	for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
        	dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    	}
	}

​ 首先是先遍历物品,再遍历背包;很显然这其实就是对于上面的二维数组的一层一层地遍历;

这里是不能更改for循环的嵌套顺序的;因为一旦先遍历背包,那么背包只会放进一个物品;

​ 其次就是在遍历背包的时候,为了防止物品多次选取,需要倒叙遍历;

​ 一旦正序遍历了,那么物品0就会被重复加入多次!

​ 举例:物品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,被放入了两次,所以不能正序遍历;

​ 由于这里所有dp[j]都等于0,正好满足倒序情况:

​ dp[2] = dp[2 - weight[0]] + value[0] = 15 (dp数组已经都初始化为0)

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

​ 5.打印dp数组:

void test02() {
    vector<int> weight(n, 0);
    vector<int> value(n, 0);
    cout << "Please enter the weight array(size is n):" << endl;
    for (int i = 0; i < n; ++i) {
        cin >> weight[i];
    }
    cout << "Please enter the value array(size is n):" << endl;
    for (int j = 0; j < n; ++j) {
        cin >> value[j];
    }
    vector<int> dp(bagspace + 1, 0);//初始化已经完成

    for (int i = 0; i < weight.size(); i++) {
        cout << "This is current dp array:" << endl;
        for (int j = bagspace; j >= weight[i]; j--) {
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
        for (int k = 0; k < bagspace + 1; k++) {
            cout << dp[k] << ' ';
        }
        cout << endl;
    }
    cout << dp[bagspace] << endl;
}

分割等和子集

给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

注意: 每个数组中的元素不会超过 100 数组的大小不会超过 200

示例 1:

  • 输入: [1, 5, 11, 5]
  • 输出: true
  • 解释: 数组可以分割成 [1, 5, 5] 和 [11].

示例 2:

  • 输入: [1, 2, 3, 5]
  • 输出: false
  • 解释: 数组不能分割成两个元素和相等的子集.

提示:

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= 100

​ 直觉第一想法自然是回溯,但是0-1背包都学了,不能不用吧)

​ 首先确定,本题的每个元素只能取一次,而不是可以重复取,所以考虑0-1背包;

​ 其次背包的weight和value均是数值本身,且背包的体积大小为sum/2,只要当背包的体积刚好为满,则返回true;

​ 思路明确,动规五部曲:

​ 1.确定dp数组含义:

​ dp[j],表示当背包容量(正整数之和)为j的时候,背包所取的最大价值(其实还是正整数之和)

​ 2.确定递推公式:

​ 对于0-1背包,dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

​ 带入此题就是,dp[j] = max(dp[j], dp[j - nums[i]] +nums[i]);

​ 3.dp数组初始化:

​ dp[0]肯定是0;

​ 如果题目给的价值都是正整数那么非0下标都初始化为0就可以了,如果题目给的价值有负数,那么非0下标就要初始化为负无穷;

这样才能让dp数组在递推的过程中取得最大的价值,而不是被初始值覆盖了

​ 本题题目中 只包含正整数的非空数组,所以非0下标的元素初始化为0就可以了;

​ 即vector<int> dp(10001, 0);

​ 4.遍历顺序:

​ 从后向前

for(int i = 0; i < nums.size(); i++) {
    for(int j = sum/2; j >= nums[i]; j--) { // 每一个元素一定是不可重复放入,所以从大到小遍历
        dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
    }
}

​ 5.打印dp数组

​ 以题例为例

image-20240509001125123

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = accumulate(nums.begin(), nums.end(), 0);
        if(sum % 2 == 1)  return false;//sum为奇数
        int target = sum/2;
        vector<int> dp(10001, 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;
    }
};

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

华为OD机试【全量和已占用字符集】(java)(100分)

1、题目描述 给定两个字符集合&#xff0c;一个是全量字符集&#xff0c;一个是已占用字符集&#xff0c;已占用字符集中的字符不能再使用。 2、输入描述 输入一个字符串 一定包含&#xff0c;前为全量字符集 后的为已占用字符集&#xff1b;已占用字符集中的字符一定是全量…

【c++】继承学习(三)菱形继承的挑战与虚拟继承的策略

&#x1f525;个人主页&#xff1a;Quitecoder &#x1f525;专栏&#xff1a;c笔记仓 朋友们大家好&#xff0c;本篇文章来讲解继承的第三部分&#xff0c;有关多继承和菱形继承的内容 目录 1.菱形继承2.虚拟继承3.虚拟继承解决数据冗余和二义性的原理4.继承的总结和反思继承…

使用ffmpeg对视频进行转码(支持浏览器播放)

在开发中&#xff0c;算法保存的mp4视频文件通过路径打开该视频发现视频播放不了&#xff0c;需要转码进行播放。使用java代码进行转码。代码如下&#xff0c;inputFilePath是转之前的视频路径&#xff0c;outputFilePath是转之后的视频路径。ffmpeg命令中libx264也可以改为其它…

分类任务的基础学习

1.什么是分类&#xff1f; 2.局限性&#xff1a; 当样本量逐渐变大的时候&#xff0c;准确率会下降——>因为线性回归曲线距离我们的原点越远&#xff0c;预测就会开始不准确&#xff0c;因为 x前面的倍数就会越来越小&#xff0c;这就导致了样本量变大&#xff0c;但是那些…

攻略:ChatGPT3.5~4.0(中文版)国内无限制免费版(附网址)【2024年5月最新更新】

一、什么是ChatGPT&#xff1f; 1、ChatGPT的全名是Chat Generative Pre-trained Transformer&#xff0c;其中"chat"表示聊天。"GPT"则是由三部分组成&#xff1a;生成式&#xff08;generative&#xff09;意味着具有创造力&#xff1b;预训练&#xff0…

PDPS15---安装教程---附安装包

目录 第1章 文件准备 1.1 安装包列表 第2章 安装Perl 2.1 Perl安装和路径选择 第3章 安装Java 3.1 Java安装和路径选择 第4章 安装Oracle 4.1 双击Setup 第5章 数据库(Oracle)和注册表(Perl) 5.1 数据库创建 5.2 注册表修改 第6章 安装Tecnomatix 6.1 安装Tecnoma…

电子硬件设计-Xilinx FPGA/SoC前期功耗评估方法(1)

目录 1. 简介 2. 使用方法 2.1 设计输入 2.2 查看结果 3. 额外说明 4. 总结 1. 简介 XPE (Xilinx Power Estimator, 功耗估算器) 电子表格是一种功耗估算工具&#xff0c;用于项目的预设计和预实现阶段。 该工具可以帮助工程师进行架构评估、器件选择、合适的电源组件以…

2024蓝桥杯CTF writeUP--爬虫协议

Dirsearch扫描网站 发现robots.txt文件 访问 直接去最后一个接口 到手

Qwen大模型实践之初体验

Qwen大模型实践之初体验 测试机器, 使用InternStudio提供的开发机&#xff0c;配置如下&#xff1a; 部分资源详细信息&#xff1a; # CPUIntel(R) Xeon(R) Platinum 8369B CPU 2.90GHz# GPU(base) rootintern-studio-50014188:~# studio-smi Running studio-smi by vgpu-smiW…

新手向的s2-046漏洞复现

一、前期准备 1.docker容器 作为第一次接触struts2漏洞类型的小白&#xff0c;第一步从搭建环境开始。首先我们需要准备一个服务器或者本地系统&#xff0c;我这里是使用本地的kali&#xff0c;kali里面需要有docker容器&#xff0c;docker容器的安装教程请自行搜索&#xff0c…

每日一题——力扣面试题 17.04. 消失的数字

题目链接&#xff1a;https://leetcode.cn/problems/missing-number-lcci/description/ 菜鸡做法&#xff1a; #include <stdlib.h> // 包含标准库头文件&#xff0c;用于内存分配等功能// 函数定义&#xff1a;寻找缺失的数字 int missingNumber(int* nums, int numsSi…

浙大×移动云,携手点亮AI新时代

近年来&#xff0c;中国移动依托强大的算网资源优势&#xff0c;围绕大模型训练、推理和应用三大场景&#xff0c;打造了一站式智算产品体系。该体系旨在为客户提供覆盖资源、平台、应用的AI全链路服务。目前&#xff0c;一站式智算产品体系已在浙江大学智算中心和许昌中原智算…

瓷器三维虚拟展示编辑平台为您量身定制高效实惠的展示方案

在竞争激烈的机械产品行业中&#xff0c;如何脱颖而出、展现产品魅力与企业实力?深圳vr公司华锐视点以其独特的三维动画设计制作服务&#xff0c;为您量身定制全方位的展示方案&#xff0c;让您的机械产品在市场中熠熠生辉。 全方位展示&#xff0c;细节尽收眼底 我们的三维展…

odoo17 音视频扩展

ODOO内置了音视频服务&#xff0c;同时也提供了与第三方平台Twilio的接口&#xff0c;用以实现音视频的扩展&#xff1a; Twilio是美国一家云通讯公司&#xff0c;算是云通讯领域的巨头企业&#xff0c;与同行业的公司以销售&营销进行投资来促进业务增长不同&#xff0c;T…

韩国站群服务器在全球网络架构中的重要作用?

韩国站群服务器在全球网络架构中的重要作用? 在全球互联网的蓬勃发展中&#xff0c;站群服务器作为网络架构的核心组成部分之一&#xff0c;扮演着至关重要的角色。韩国站群服务器以其卓越的技术实力、优越的地理位置、稳定的网络基础设施和强大的安全保障能力&#xff0c;成…

深度学习中的注意力机制二(Pytorch 16)

一 Bahdanau 注意力 通过设计一个 基于两个循环神经网络的编码器‐解码器架构&#xff0c;用于序列到序列学习。具体来说&#xff0c;循环神经网络编码器将长度可变的序列转换为固定形状的上下文变量&#xff0c;然后循环神经网络 解码器根据生成的词元和上下文变量按词元生成…

转转小程序数据处理

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01;wx a15018601872&#xff0c;x30184483x…

今天遇到一个GPT解决不了的问题

问题描述 你好&#xff0c;postman的一个post请求&#xff0c;编辑器里面放了一个很长的json数据&#xff0c;报Tokenization is skipped for long lines for performance reasons. This can be configured via editor.maxTokenizationLineLength.&#xff0c;但是同样的数据&a…

YOLOv9全网最新改进系列:YOLOv9完美融合标准化的注意力模块NAM,高效且轻量级的归一化注意力机制,助力目标检测再上新台阶!

YOLOv9全网最新改进系列&#xff1a;YOLOv9完美融合标准化的注意力模块NAM&#xff0c;高效且轻量级的归一化注意力机制&#xff0c;助力目标检测再上新台阶&#xff01;&#xff01;&#xff01; YOLOv9原文链接戳这里&#xff0c;原文全文翻译请关注B站Ai学术叫叫首er B站全…

基于模糊控制的AMT自动变速汽车换档智能控制系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于模糊控制的AMT自动变速汽车换档智能控制系统simulink建模与仿真。 2.系统仿真结果 输入的V&#xff0c;Ac&#xff0c;a 输出的档位&#xff1a; 3.核心程序与模型 版…