动态规划part03

news/2024/12/16 22:37:38/

文章参考来源代码随想录

题目参考来源leetcode

01背包问题  二维:

背包问题:

 动规五部曲:

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

其实这里由题目要我们求的就可以推出来了

把下标为0-i的物品装入容量为j的背包最大价值为dp[i][j]

具体分析:

我们需要使用二维数组,为什么呢?

因为有两个维度需要分别表示:物品 和 背包容量

如图,二维数组为 dp[i][j]。

<a class=动态规划-背包问题1" height="494" src="https://i-blog.csdnimg.cn/img_convert/aaccc267b6c658008a52eacbe02ca4a3.png" width="912" />

那么这里 i 、j、dp[i][j] 分别表示什么呢?

i 来表示物品、j表示背包容量。

我们来尝试把上面的 二维表格填写一下。

动态规划的思路是根据子问题的求解推导出整体的最优解。

我们先看把物品0 放入背包的情况:

背包容量为0,放不下物品0,此时背包里的价值为0。

背包容量为1,可以放下物品0,此时背包里的价值为15.

背包容量为2,依然可以放下物品0 (注意 01背包里物品只有一个),此时背包里的价值为15。

以此类推。

再看把物品1 放入背包:

背包容量为 0,放不下物品0 或者物品1,此时背包里的价值为0。

背包容量为 1,只能放下物品0,背包里的价值为15。

背包容量为 2,只能放下物品0,背包里的价值为15。

背包容量为 3,上一行同一状态,背包只能放物品0,这次也可以选择物品1了,背包可以放物品1 或者 物品0,物品1价值更大,背包里的价值为20。

背包容量为 4,上一行同一状态,背包只能放物品0,这次也可以选择物品1了,背包可以放下物品0 和 物品1,背包价值为35。

以上举例,是比较容易看懂,我主要是通过这个例子,来帮助大家明确dp数组的含义。

上图中,我们看 dp[1][4] 表示什么意思呢。

任取 物品0,物品1 放进容量为4的背包里,最大价值是 dp[1][4]。

通过这个举例,我们来进一步明确dp数组的含义。

dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少

2.确定递推公式:

当前背包容量为j放入物品i,这里可以分成两种情况:

背包空间不够放入物品i:取不放物品i时的最大价值dp[i-1][j];

足够放入物品i:比较不放物品i时的最大价值dp[i-1][j]和放入后的总价值dp[i-1][j-weigh[i]]+value[i];

(可以理解为没放入之前已有的最大价值dp[i-1][j-weigh[i]]加上当前物品的价值)因此这里的递推公式为dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])

具体分析:

首先我们要明确有哪些方向可以推导出 dp[i][j]。

这里我们dp[1][4]的状态来举例:

求取 dp[1][4] 有两种情况:

  1. 放物品1
  2. 还是不放物品1

如果不放物品1, 那么背包的价值应该是 dp[0][4] 即 容量为4的背包,只放物品0的情况。

推导方向如图:

如果放物品1, 那么背包要先留出物品1的容量,目前容量是4,物品1 的容量(就是物品1的重量)为3,此时背包剩下容量为1。

容量为1,只考虑放物品0 的最大价值是 dp[0][1],这个值我们之前就计算过。

所以 放物品1 的情况 = dp[0][1] + 物品1 的价值,推导方向如图:

两种情况,分别是放物品1 和 不放物品1,我们要取最大值(毕竟求的是最大价值)

dp[1][4] = max(dp[0][4], dp[0][1] + 物品1 的价值)

 3.dp数组如何初始化:

第一列:表示背包容量为0时放入下标0到i的物品,无意义,因此第一列不用初始化(或者全初始化为0)

第一行:表示背包容量为0到j时放入下标为0的物品,当背包容量大于物品0的重量时,才能放入。因此这里从物品0的重量背包总重量全都初始化为物品0的价值

4.确定遍历顺序:

在如下图中,可以看出,有两个遍历的维度:物品与背包重量

<a class=动态规划-背包问题3" height="466" src="https://i-blog.csdnimg.cn/img_convert/7dd0da3580b35aedfd0f021bc4629249.png" width="866" />

那么问题来了,先遍历 物品还是先遍历背包重量呢

其实都可以(虽然两个for循环遍历的次序不同,其实根本不影响dp[i][j]公式的推导), 但是先遍历物品更好理解。(把物品i放入背包容量从0到背包总重所能存入的最大价值)

注意这里物品从1开始遍历,因为物品0已经初始化过了

要理解递归的本质和递推的方向

dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 递归公式中可以看出dp[i][j]是靠dp[i-1][j]和dp[i - 1][j - weight[i]]推导出来的。

dp[i-1][j]和dp[i - 1][j - weight[i]] 都在dp[i][j]的左上角方向(包括正上方向),那么先遍历物品,再遍历背包的过程如图所示:

<a class=动态规划-背包问题5" height="492" src="https://i-blog.csdnimg.cn/img_convert/8f8623cae73022aba5564c8392bf7140.png" width="892" />

5.举例推导dp数组:

来看一下对应的dp数组的数值,如图:

<a class=动态规划-背包问题4" height="440" src="https://i-blog.csdnimg.cn/img_convert/fee6c2fdaa239f0a61a7947ac276e3de.jpeg" width="850" />

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

#include <bits/stdc++.h>
using namespace std;
int main(){int n,bagw;cin>>n>>bagw;vector<int>value(n,0) ;vector<int>weigh(n,0);for(int i=0;i<n;i++)cin>>weigh[i];for(int i=0;i<n;i++)cin>>value[i];vector<vector<int>>dp(n,vector<int>(bagw+1,0));for(int j=weigh[0];j<=bagw;j++){dp[0][j]=value[0];}for(int i=1;i<n;i++){for(int j=0;j<=bagw;j++){if(j-weigh[i]<0)dp[i][j]=dp[i-1][j];else  dp[i][j]=max(dp[i-1][j],dp[i-1][j-weigh[i]]+value[i]);}}cout<< dp[n-1][bagw]<<endl; return 0;
}

01背包问题 一维:

对于背包问题其实状态都是可以压缩的。

其实可以发现如果把dp[i - 1]那一层拷贝到dp[i]上,表达式完全可以是:dp[i][j] = max(dp[i][j], dp[i][j - weight[i]] + value[i]);

与其把dp[i - 1]这一层拷贝到dp[i]上,不如只用一个一维数组了,只用dp[j](一维数组,也可以理解是一个滚动数组)。

动规五部曲:

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

dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少

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

2.确定递推公式:

二维dp数组的递推公式为dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])

一维把物品下标优化掉了,直接把上一层的拷贝到这一层就好了,

所以在 上面递推公式的基础上,去掉i这个维度就好。

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

3.dp数组如何初始化:

关于初始化,一定要和dp数组的定义吻合,否则到递推公式的时候就会越来越乱

dp在推导时一定取的是最大值,如果题目给的价值都是正整数那么非0下标都初始化为0就可以了。这样才能让dp数组在递归公式的过程中取的最大的价值,而不是被初始值覆盖了。

因此全部初始化为0

4.确定遍历顺序:

这里和二维有比较大的区别了

首先这里在遍历背包时要从大(背包总重)到小(物品i的重量)以保证物品i只被放入一次

如果正序遍历

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

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

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

为什么倒序遍历,就可以保证物品只放入一次呢?

倒序就是先算dp[2]

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

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

所以从后往前循环,每次取得状态不会和之前取得状态重合,这样每种物品就只取一次了。

而对于二维dp,dp[i][j]都是通过上一层即dp[i - 1][j]计算而来,本层的dp[i][j]并不会被覆盖

因此这里二维遍历背包时是要从0到背包总重(如果从物品i重量到背包总重的话,遍历下一层/下一个物品时会出现上一层没有数据可以用的情况)

以及物品从下标0开始遍历,因为二维初始化了第一行,而一维创建数组的时候默认为0了,所以这里是没有遍历过物品0的

代码中是先遍历物品嵌套遍历背包容量的,如果遍历背包容量放在上一层,那么每个dp[j]就只会放入一个物品,即:背包里只放入了一个物品。

5.举例推导dp数组:

一维dp,分别用物品0,物品1,物品2 来遍历背包,最终得到结果如下:

<a class=动态规划-背包问题9" height="482" src="https://i-blog.csdnimg.cn/img_convert/b36726918bf2027b03f92e2743114e29.png" width="890" />

#include <bits/stdc++.h>
using namespace std;
int main(){int n,bagw;cin>>n>>bagw;vector<int>value(n,0) ;vector<int>weigh(n,0);for(int i=0;i<n;i++)cin>>weigh[i];for(int i=0;i<n;i++)cin>>value[i];vector<int>dp(bagw+1,0);for(int i=0;i<n;i++){for(int j=bagw;j>=weigh[i];j--){dp[j]=max(dp[j],dp[j-weigh[i]]+value[i]);}}cout<< dp[bagw]<<endl; return 0;
}

 416. 分割等和子集

背包问题有多种背包方式,常见的有:01背包、完全背包、多重背包、分组背包和混合背包等等。

要注意题目描述中商品是不是可以重复放入。

即一个商品如果可以重复多次放入是完全背包,而只能放入一次是01背包,写法还是不一样的。

要明确本题中我们要使用的是01背包,因为元素我们只能用一次。

这道题目是要找是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。 

那么只要找到集合里能够出现 sum / 2 的子集总和,就算是可以分割成两个相同元素和子集了。

 

只有确定了如下四点,才能把01背包问题套到本题上来。

  • 背包的容量为sum / 2
  • 背包要放入的商品(集合里的元素)重量为 元素的数值,价值也为元素的数值
  • 背包如果正好装满,说明找到了总和为 sum / 2 的子集。
  • 背包中每一个元素是不可重复放入。

以上分析完,我们就可以套用01背包,来解决这个问题了。

动规五部曲:

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

dp[j]表示 背包总容量(所能装的总重量)是j,放进物品后,背的最大重量(之前这里是价值)为dp[j]。

那么如果背包容量为target, dp[target]就是装满 背包之后的重量(之前这里是价值),所以 当 dp[target] == target 的时候,背包就装满了。(因为本题元素价值就是重量)理解这点很重要

装不满的情况:

拿输入数组 [1, 5, 11, 5],举例, dp[7] 只能等于 6,因为 只能放进 1 和 5。

而dp[6] 就可以等于6了,放进1 和 5,那么dp[6] == 6,说明背包装满了。

2.确定递推公式:

01背包的递推公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

本题,相当于背包里放入数值,那么物品i的重量是nums[i],其价值也是nums[i]。

所以递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);

3.dp数组如何初始化:

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

4.确定遍历顺序:

具体见上题

5.举例推导dp数组:

dp[j]的数值一定是小于等于j的。

如果dp[j] == j 说明,集合中的子集总和正好可以凑成总和j,理解这一点很重要。

用例1,输入[1,5,11,5] 为例,如图:

416.分割等和子集2

最后dp[11] == 11,说明可以将这个数组分割成两个子集,使得两个子集的元素和相等。

class Solution {
public:bool canPartition(vector<int>& nums) {int sum=0;vector<int>dp(10001,0);for (int i = 0; i < nums.size(); i++) {sum += nums[i];}if(sum%2==1)return false;int target=sum/2;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]);}}return dp[target] == target? true:false;}
};


http://www.ppmy.cn/news/1555698.html

相关文章

Mac/Windows端长期破解myBase8方法(无需安装火绒)

提醒 不管哪个端&#xff0c;都需要先退出myBase。 Mac 进入用户根目录/Users/c0ny100&#xff0c;即下边是Macintosh HD > 用户 > [你的用户名]这个界面然后按ShiftCommond.&#xff0c;显示隐藏文件。找到.Mybase8.ini文件 打开.Mybase8.ini文件&#xff0c;删除Fir…

机器学习—大语言模型:推动AI新时代的引擎

云边有个稻草人-CSDN博客 目录 引言 一、大语言模型的基本原理 1. 什么是大语言模型&#xff1f; 2. Transformer 架构 3. 模型训练 二、大语言模型的应用场景 1. 文本生成 2. 问答系统 3. 编码助手 4. 多语言翻译 三、大语言模型的最新进展 1. GPT-4 2. 开源模型 …

基于python的一个简单的压力测试(DDoS)脚本

DDoS测试脚本 声明&#xff1a;本文所涉及代码仅供学习使用&#xff0c;任何人利用此造成的一切后果与本人无关 源码 import requests import threading# 目标URL target_url "http://47.121.xxx.xxx/"# 发送请求的函数 def send_request():while True:try:respo…

Reactor 响应式编程(第一篇:Reactor核心)

系列文章目录 Reactor 响应式编程&#xff08;第一篇&#xff1a;Reactor核心&#xff09; Reactor 响应式编程&#xff08;第二篇&#xff1a;Spring Webflux&#xff09; Reactor 响应式编程&#xff08;第三篇&#xff1a;R2DBC&#xff09; Reactor 响应式编程&#xff08…

FPGA 17 ,FPGA 与 SR-IOV虚拟化技术,高性能计算与虚拟化技术的结合(FPGA 与 SR-IOV 和 PCI,高性能计算与虚拟化的完美融合)

目录 前言 一. SR-IOV 的起源与发展 1. SR-IOV 的起源与时间线 2. SR-IOV 的诞生原因 3. SR-IOV 的详细介绍 二. SR-IOV 和 PCI 之间的关系 三. PCI 的起源与演进 1. PCI 的起源与时间线 2. PCI 的关键特性 四. FPGA 的独特魅力 1. FPGA 的定义与特性 2. FPGA 的内…

【深度学习量化交易7】miniQMT快速上手教程案例集——使用xtQuant进行历史数据下载篇

我是Mr.看海&#xff0c;我在尝试用信号处理的知识积累和思考方式做量化交易&#xff0c;应用深度学习和AI实现股票自动交易&#xff0c;目的是实现财务自由~ 目前我正在开发基于miniQMT的量化交易系统。 在前几篇的文章中讲到&#xff0c;我正在开发的看海量化交易系统&#x…

Web前端技术宝典:期末冲刺指南

本文将为大家整理一份 Web 前端期末复习资料&#xff0c;内容涵盖 HTML、CSS、JavaScript 和常用的前端框架等方面的知识&#xff0c;帮助大家高效复习。 Web前端技术宝典&#xff1a;期末冲刺指南 1. HTML基础2. CSS基础3. JavaScript基础4. 前端框架5. 常见考试题型结语 1. …

SpringBoot【八】mybatis-plus条件构造器使用手册!

一、前言&#x1f525; 环境说明&#xff1a;Windows10 Idea2021.3.2 Jdk1.8 SpringBoot 2.3.1.RELEASE 经过上一期的mybatis-plus 入门教学&#xff0c;想必大家对它不是非常陌生了吧&#xff0c;这期呢&#xff0c;我主要是围绕以下几点展开&#xff0c;重点给大家介绍 里…