Day42【动态规划】背包问题详解、416.分割等和子集

news2024/9/28 13:21:33

0-1背包问题详解:二维数组

 

文章讲解

视频讲解

0-1 背包问题:有 n 件物品和一个最多能背重量为 w 的背包。第 i 件物品的重量是 weight[i],价值是 value[i],每件物品只能用一次,求解将物品装入背包里物品价值总和最大为多少

例: 

背包最大重量为4

物品为:

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

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

依然动态规划五部曲!

1、确定 dp 数组下标及值的含义 

dp[i][j]:下标 i, j 表示从下标为 [0 - i] 的物品中任意取放进容量为 j 的背包,dp[i][j] 的值表示从物品下标 [0 - i] 任取物品放进容量为 j 的背包所能装入的最大价值

2、确定递推公式 

dp[i][j] 的值表示从下标为 [0 - i] 的物品中任取,放进容量为 j 的背包所能装入的最大价值,怎么求这个 dp[i][j] 呢?为了求 dp[i][j],肯定需要考虑从下标为 0 到 i 的物品中取物然后装进容量为 j 的背包的装取方案。得到这个 dp[i][j] 的装取方案有两种:一种是不放物品 i 就能得到最大价值,另一种是放了物品 i 才能得到最大价值

  • 不放物品 i:已经确定里面不放物品 i 的最大价值,为物品 0 到 i - 1 装进容量为 j 的背包的最大价值,即 dp[i - 1][j]
  • 放物品 i:已经确定要放物品 i 时的最大价值,为物品 0 到 i - 1 装进容量为 j - weight[i] 的背包的最大价值加上物品 i 的价值,即 dp[i - 1][j - weight[i]] + value[i]

两种装取方案中的最大值就是从物品 [0 - i] 任取物品放进容量为 j 的背包所能装入的最大价值,即递推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])  

注意这个递推公式的前提条件是背包要能装下物品 i,否则只需要考虑不放物品 i 的方案

3、dp 数组初始化

由递推公式,我们首先观察到 dp[i][j] 的值都是由上一行(层)的值推出来的(即由 i - 1 那一行推出来),故一定要初始化第一行,即 dp[0][j] 需要被初始化

怎么初始化 dp[0][j] 呢?考虑 dp 数组下标及值的含义:取物品 0(从下标为 0 到 0 的物品中取)放进容量为 j 的背包,能装入的最大价值 

显而易见

其他位置随意初始化,反正都会被遍历覆盖

4、确定遍历顺序 

从上层(上一行)遍历填充到下层(下一行)就可以(因为当前层的值是由上一层推出来的,需要保证上一层是已经更新后的正确的值),单层中从左到右或者从右到左遍历填充均可 

代码中,外层 for 循环遍历物品即可(一个物品的索引代表一行,一行一行遍历) 

5、打印 dp 数组验证

代码如下

void bag_problem_2d() {
    cout << "请输入背包容量:";
    int bagSize;
    cin >> bagSize;
    cout << "请输入物品个数:";
    int n;
    cin >> n;
    cout << "请依次输入物品重量:" << endl;
    vector<int> weight(n);
    for (int i = 0; i < n; ++i) {
        cin >> weight[i];
    }
    cout << "请依次输入物品价值:" << endl;
    vector<int> value(n);
    for (int i = 0; i < n; ++i) {
        cin >> value[i];
    }
    cout << "背包容量:" << bagSize << endl;
    cout << "物品重量:";
    for (auto i : weight)
        cout << i << "\t\t";
    cout << endl;
    cout << "物品价值:";
    for (auto i : value)
        cout << i << "\t\t";
    cout << endl << endl;
    

    // 定义dp数组下标及含义:dp[i][j]:从物品0到i中任取,放进容量为bagSize的背包,得到最大价值为dp[i][j]
    vector<vector<int> > dp(n, vector<int>(bagSize + 1, 12345));	// 12345表示其他位置随意初始化都行 
    // 递推公式:dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]]+value[i])
    // 初始化:初始化第一行,即i=0那一行即可
    for (int j = 0; j <= bagSize; ++j) {
        if (j >= weight[0]) // 当能装下物品0,才装
            dp[0][j] = value[0];
        else    // 装不下物品0
            dp[0][j] = 0;
    }
    // 遍历填充dp数组:一行一行填充
    for (int i = 1; i < weight.size(); ++i) // 遍历物品(即遍历每一行)
        for (int j = 0; j <= bagSize; ++j) // 遍历背包(遍历行中的元素)
        {
            if (j >= weight[i])
                dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]]+value[i]);
            else
                dp[i][j] = dp[i-1][j];
        }
    // 打印dp数组验证
    cout << "dp数组如下:" << endl;
    for (const auto & line : dp) {
        for (const auto item : line)
            cout << item << "\t\t";
        cout << endl;
    }
    return;
}

0-1背包问题详解:滚动数组 

文章讲解

视频讲解

上面用了二维 dp 数组实现 0-1 背包问题,有没有办法优化,用一维 dp 数组实现? 

回顾一下二维的递推公式(前提条件是背包要能装下物品 i

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

重点在于:dp[i][j] 的值都是由上一行(层)的值推出来

即更新当前层的时候,只需要用到上一层的数据就行了。我们能不能只维护一层的数据?

只维护红框中的一层数据 

我们将 dp 数组压缩成一行 

先看看压缩后的递推公式:因为状态压缩了,dp 数组变成了一维,下标 j 代表背包容量

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

等式左边的 dp[j] 代表的是第 i 层的dp,这是更新后的dp;右边的 dp[j] 代表的是第 i-1 层的 dp, dp[j-w[i]] 代表的是第 i-1 层的 dp

左图为原二维 dp 数组,右图是我们压缩后的滚动数组 

本质上就是就地更新 dp 数组的每一层 

我们从图中可以看出几个关键点

  • 因为滚动数组维护的是单层二维 dp 数组的数据,为了实现一层一层遍历,外层 for 循环为物品 i,表示在二维数组中需要一层一层填充,每遍历完一轮外层的循环,表明更新完了一层二维 dp 数组
  • 更新每层中的元素时,即更新我们的一维数组时,需要倒序遍历填充!因为我们更新当前层某位置元素依赖的是上一层元素,更详细地说是正上方及正上方左边的元素,对应到滚动数组中,更新某位置元素依赖的是当前位置元素的旧值及当前位置左边的元素的旧值(这里的旧值指的是上一轮外层 for 循环所填充的值)。只有倒序遍历从右向左填充,才能保证当前位置及当前位置左边的元素保留上一轮外层循环的旧值。如果正序遍历,则当前位置的左边元素的值可能在本轮外层循环中已经被更新了,无法持有上一轮循环更新得到的值了
  • 另外提一下,如果正序遍历,相当于物品能够多次添加(这个后面再展开讲解)

代码如下:

void bag_problem_1d() {
    cout << "请输入背包容量:";
    int bagSize;
    cin >> bagSize;
    cout << "请输入物品个数:";
    int n;
    cin >> n;
    cout << "请依次输入物品重量:" << endl;
    vector<int> weight(n);
    for (int i = 0; i < n; ++i) {
        cin >> weight[i];
    }
    cout << "请依次输入物品价值:" << endl;
    vector<int> value(n);
    for (int i = 0; i < n; ++i) {
        cin >> value[i];
    }
    cout << "背包容量:" << bagSize << endl;
    cout << "物品重量:";
    for (auto i : weight)
        cout << i << "\t\t";
    cout << endl;
    cout << "物品价值:";
    for (auto i : value)
        cout << i << "\t\t";
    cout << endl << endl;
    

    // 定义状态压缩后的dp数组
    vector<int> dp(bagSize + 1);
    // 递推公式:dp[j] = max(dp[j], dp[j-weight[i]]+value[i])
    // 初始化:初始化第一行,i=0那一行,目前dp的值就是第一行 
    for (int j = 0; j <= bagSize; ++j) {
        if (j >= weight[0]) // 当能装下物品0,才装
            dp[j] = value[0];
        else    // 装不下物品0
            dp[j] = 0;
    }
    // 打印一下第一行的dp数组值
	for (auto item : dp)
		cout << item << "\t\t";
	cout << endl; 
    // 遍历填充dp数组:一行一行填充
    for (int i = 1; i < weight.size(); ++i) // 遍历物品(一层一层遍历,第一行已经被初始化了,从第二行开始) 
    {
    	for (int j = bagSize; j >= 0; --j) // 遍历背包(倒序遍历) 
        {
            if (j >= weight[i])	// 当能装下物品i才装 
                dp[j] = max(dp[j], dp[j-weight[i]]+value[i]);
//            else	// 装不下物品i 
//                dp[j] = dp[j];
        }
        // 遍历完了一轮外层循环,我们打印看看这层dp数组 
        for (auto item : dp)
        	cout << item << "\t\t";
        cout << endl;
	} 
    return;
}

416.分割等和子集

力扣题目链接/文章讲解

视频讲解

本题能够将问题转化为 0-1 背包问题

0-1背包问题是用物品装入背包,求装入的最大价值

要把01背包问题套到本题上来,需要确定

  • 背包容量
  • 物品价值
  • 物品重量

怎么转化呢?回归主题:首先,本题要求集合里能否出现总和为 sum / 2 的子集

假如我们将元素看成一定重量的物品,问题转化成想将这些具有一定重量的物品装入容量为 sum / 2 的背包,看看能不能恰好装满

这种应该怎么设置物品重量和价值?答:重量为元素的数值,价值也为元素的数值

这样,可装入的最大价值 = 可装入的最大重量,可以通过装入的最大价值(可装入的最大重量)是否和容量相等判断是否能装满

这样问题就转过来了: 

  • 背包容量:sum / 2
  • 物品价值:元素数值
  • 物品重量:元素数值

接下来可以开始套用0-1背包的方法 

代码如下:

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = accumulate(nums.begin(), nums.end(), 0);
        if (sum % 2)    // 如果sum为奇数,则肯定无法分成两个相等子集
            return false;
        // 背包大小 sum / 2
        // 物品重量 nums
        // 物品价值 nums
        int bagSize = sum / 2;
        vector<int> dp(bagSize + 1);
        for (int j = 0; j <= bagSize; ++j) {    // 初始化二维数组第一行
            if (j >= nums[0])   // 如果容量为 j 的背包能装下物品0
                dp[j] = nums[0];
            else
                dp[j] = 0;
        }
        for (int i = 1; i < nums.size(); ++i) { // 遍历物品(一层一层遍历)
            for (int j = bagSize; j >= 0; --j) {    // 倒序遍历背包(从右向左遍历层中的元素)
                if (j > nums[i]) {  // 如果容量 j 能装下物品
                    dp[j] = max(dp[j], dp[j-nums[i]] + nums[i]);
                }
                else    // 不装物品
                    dp[j] = dp[j];
            }
        }
        return dp[bagSize] == bagSize;
    }
};

回顾总结 

时刻记住滚动数组是如何与二维 dp 对应的

具体而言在操作滚动数组的某个位置的时候,脑海中要能对应出操作的是二维数组中的哪个位置

此外,一定要记住二维数组中行和列索引分别代表的是物品还是背包容量 

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

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

相关文章

深入理解 Go sync.Map

前言 Go 语言原生 map 并不是线程安全的&#xff0c;要对它进行并发读写操作时&#xff0c;一般有两种选择&#xff1a; 原生map搭配Mutex或RWMutex使用sync.Map 和原生map搭配Mutex或RWMutex相比&#xff0c;sync.Map在以下场景更有优势&#xff1a; 读多写少 修改&#x…

Golang 1.18 新特性模糊测试

一、Go 1.18新特性一览 0.官方博客&#xff1a;跳转 1.支持泛型 2.模糊测试 3.工作空间 4.性能提升 二、模糊测试介绍 模糊测试 &#xff08;fuzz testing, fuzzing&#xff09;是一种软件测试技术。其核心思想是將自动或半自动生成的随机数据输入到一个程序中&#xff0…

chatgpt赋能Python-python3怎么保存

Python 3如何高效地保存数据 Python是一门广泛使用的编程语言之一&#xff0c;其强大的工具和库使其成为从数据分析到机器学习等领域的首选语言。在处理大量数据时&#xff0c;数据的存储和访问变得非常关键。在这篇文章中&#xff0c;我们将讨论Python 3中如何保存数据以提高…

delphi6安装手册

DELPHI6安装手册 安装delphi6软件&#xff1a; 运行&#xff1a;\\dev2000 选择&#xff1a;set up→语言及开发工具→DELPHI6→双击图标install.exe 先后安装Delphi6和TeamSource 安装delphi6时会出现的要填序列号的情况 delphi6的安装序列号&#xff1a;在同一路径下双击ke…

chatgpt赋能Python-python3_9怎么调成黑色背景

Python是一种高级编程语言&#xff0c;它的版本不断发展和改进。最新发布的Python 3.9版本为用户提供了更多的新特性和改进&#xff0c;其中包括能够自定义代码编辑器背景颜色的新功能。本篇文章将介绍如何在Python 3.9中调整编辑器背景颜色为黑色&#xff0c;并探讨这个功能的…

三十九、分布式事务、seata、配置微服务客户端

1、事务 事务(TRANSACTION)是作为单个逻辑工作单元执行的一系列SQL操作&#xff0c;这些操作作为一个整体一起向系统提交&#xff0c;要么都执行、要么都不执行。 1.1 ACID事务的特点 原子性: 一致性&#xff1a;隔离性持久性 1.2 事务并发带来的问题 脏读 幻读 不可重复读 …

TCL字符串操作

format命令 因为 TCL 把所有的输入都当作字符串看待&#xff0c;所以 TCL 提供了较强的字符串操作功能&#xff0c;TCL 中与 字符串操作有关的命令有&#xff1a;string、format、regexp、regsub、scan 等。 语法&#xff1a;format formatstring ?vlue value...? format …

【Redis】电商项目秒杀问题之下单接口优化:Redis缓存、MQ以及lua脚本优化高并发背景下的秒杀下单问题

目录 一、优化思路 二、缓存库存与订单 1、库存缓存的redis数据结构 2、订单信息缓存的redis数据结构 三、整体流程 四、lua脚本确保权限校验操作的原子性 一、优化思路 【Redis】电商项目秒杀问题之超卖问题与一人一单问题_1373i的博客-CSDN博客https://blog.csdn.net/q…

chatgpt赋能Python-python3_打印

Python3 打印&#xff1a;一篇介绍性SEO文章 如果你是一名Python编程工程师&#xff0c;那么你一定知道在Python中打印是一项基本技能。在Python3中&#xff0c;打印已经发生了一些变化&#xff0c;本文将介绍Python3中打印的新特性和使用方法&#xff0c;并为您提供一些最佳实…

算法设计与分析:随机化算法(作业-必做)(头歌实验)

第1关&#xff1a;硬币实验 任务描述 本关任务&#xff1a;计算机产生的伪随机数来模拟抛硬币试验。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a;1.如何获取数组的长度&#xff0c;2.如何遍历数组。 随机数 随机数在随机化算法设计中扮演着十分重要的角…

云原生CAx软件:gRPC

gRPC是Google基于HTTP/2协议开发的一套开源、跨平台的高性能RPC框架&#xff0c;可用于连接微服务架构内的各种服务&#xff0c;亦可以连接客户端与后端服务。 Ref. from gRPC gRPC is a modern open source high performance Remote Procedure Call (RPC) framework that can…

策划能力提升攻略:让你成为行业大咖

策划能力的提高是没有立竿见影的&#xff0c;首先你了解策划的本质吗?了解市场营销的本质吗?了解战略和策略的关系吗? 不是经常把什么IMC/USP/4P/4C/DNA/核心价值挂在嘴边&#xff0c;会做做波特SWOT分析、用用BCG的模型、MINKSY的7S模型这些就是策划了的。 别人的理论你可…

WEB AK赛

文章目录 web1_观字SSRF常见的URL绕过方式 web2_观星web3_观图web4_观心签到_观己 web1_观字 <?php#flag in http://192.168.7.68/flag if(isset($_GET[url])){$url $_GET[url];$protocol substr($url, 0,7);if($protocol!http://){die(仅限http协议访问);}if(preg_matc…

安装Maven 3.6.1:图文详细教程(适用于Windows系统)

一、官网下载对应版本 推荐使用maven3.6.1版本&#xff0c;对应下载链接&#xff1a; Maven3.6.1下载地址 或者&#xff0c;这里提供csdn下载地址&#xff0c;点击下载即可&#xff1a; Maven3.6.1直链下载 其他版本下载地址&#xff1a; 进入网址&#xff1a;http://mave…

【Linux之IO系统编程学习】01.open函数使用 代码实现touch命令效果

【Linux之IO系统编程学习】 项目代码获取&#xff1a;https://gitee.com/chenshao777/linux_-io.git &#xff08;麻烦点个免费的Star哦&#xff0c;您的Star就是我的写作动力&#xff01;&#xff09; 01.open函数使用 & 代码实现touch命令 一、open函数&#xff08;ma…

ARM处理器概论与组织

目录 1.ARM产品系列 2.体系结构 3.ARM指令集 定义&#xff1a; ARM的指令集&#xff1a; 4.编译原理 5.ARM存储模型 6.ARM的8种工作方式 ARM&#xff08;Advanced RISC Machines&#xff09;有三种含义 一个公司的名称、一类处理器的通称、一种技术&#xff0c;我们在这…

【中阳期货】人工智能AI与期货有什么 关系

人工智能&#xff08;AI&#xff09;和期货交易之间有许多相互影响的因素。AI可以帮助期货交易者在交易决策中更好地应对大量数据&#xff0c;加强交易系统预测能力&#xff0c;优化资产配置策略。以下是AI与期货交易的一些具体关系&#xff1a; 数据分析&#xff1a;AI有能力高…

python pickle反序列化分析

文章目录 前言Pickle的作用pickle反序列化pickletools和反序列化流程漏洞产生(__reduce__)R指令的绕过通过i和o指令触发 总结 前言 春秋杯中遇到了一道python题&#xff0c;使用的了numpy.loads()触发反序列化漏洞&#xff0c;百度学习了一下&#xff0c;发现numpy.load()会先…

【mysqlbinlog 恢复数据】

不小心把数据删掉了 首先要拿到binlog文件 命令行执行 /usr/local/mysql/bin/mysqlbinlog --base64-outputdecode-rows --start-datetime"2023-05-19 09:01:32" --stop-datetime"2023-05-19 09:01:35" -v /Users/zylong/Downloads/mysql-bin.003178 --re…

动态规划-状态机模型

大盗阿福 题目 链接&#xff1a;https://www.acwing.com/problem/content/1051/ 阿福是一名经验丰富的大盗。趁着月黑风高&#xff0c;阿福打算今晚洗劫一条街上的店铺。 这条街上一共有 N N N 家店铺&#xff0c;每家店中都有一些现金。 阿福事先调查得知&#xff0c;只…