DAY35|动态规划Part03|LeetCode:01背包问题 二维、01背包问题 一维、416. 分割等和子集
目录
01背包理论基础(一)
基本思路
C++代码
01背包理论基础(二)
基本思路
C++代码
LeetCode:416. 分割等和子集
基本思路
C++代码
01背包理论基础(一)
题目链接:卡码网46. 携带研究材料
文字讲解:01背包理论基础
视频讲解:带你学透0-1背包问题!
对于面试的话,其实掌握01背包和完全背包,就够用了,最多可以再来一个多重背包。
背包问题的理论基础重中之重是01背包,一定要理解透!
首先对于暴力解法,每一件物品其实只有两个状态,取或者不取,所以可以使用回溯法搜索出所有的情况,那么时间复杂度就是O(2^n),这里的n表示物品数量。很容易超时,因此需要使用动态规划的方法进行优化!
以下表为例,问背包能背的物品最大价值是多少?
重量 | 价值 | |
物品0 | 1 | 15 |
物品1 | 3 | 20 |
物品2 | 4 | 30 |
基本思路
动规五部曲分析如下:
- 确定dp数组以及下标的含义
我们需要使用二维数组,为什么呢?因为有两个维度需要分别表示:物品和背包容量。如图,二维数组为 dp[i][j]。
其中,i 来表示物品、j表示背包容量。dp[i][j]表示在物品[0,i]中,任意选取一个或多个物品,装进容量为j的背包中时能够满足条件的最大价值。
- 确定递推公式
对于第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]);
- 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
vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));
for (int j = weight[0]; j <= bagweight; j++) {dp[0][j] = value[0];
}
- 确定遍历顺序
先遍历 物品还是先遍历背包重量呢?其实都可以!! 但是先遍历物品更好理解。
//先遍历物品,在遍历背包
// 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]);}
}//先遍历背包,在遍历物品
// 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]);}
}
- 举例推导dp数组
来看一下对应的dp数组的数值,如图:
最终结果就是dp[2][4]。
C++代码
#include <bits/stdc++.h>
using namespace std;int main() {int n, bagweight;// 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];}// dp数组, dp[i][j]代表行李箱空间为j的情况下,从下标为[0, i]的物品里面任意取,能达到的最大价值vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));// 初始化, 因为需要用到dp[i - 1]的值// j < weight[0]已在上方被初始化为0// j >= weight[0]的值就初始化为value[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 = 0; j <= bagweight; j++) { // 遍历行李箱容量if (j < weight[i]) dp[i][j] = dp[i - 1][j]; // 如果装不下这个物品,那么就继承dp[i - 1][j]的值else {dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);}}}cout << dp[n - 1][bagweight] << endl;return 0;
}
01背包理论基础(二)
题目链接:卡码网46. 携带研究材料
文字讲解:01背包理论基础(二)
视频讲解:带你学透01背包问题(滚动数组篇)
基本思路
通过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](一维数组,也可以理解是一个滚动数组)。
进一步,动规五部曲分析如下:
- 确定dp数组以及下标的含义
在一维dp数组中,dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]。
- 确定递推公式
一维dp数组,其实就上一层 dp[i-1] 这一层 拷贝的 dp[i]来。
递推公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
- dp数组如何初始化
dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j],那么dp[0]就应该是0,因为背包容量为0所背的物品的最大价值就是0。根据上面的递推公式,dp数组在推导的时候一定是取价值最大的数,如果题目给的价值都是正整数那么非0下标都初始化为0就可以了。
- 确定遍历顺序
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]);}
}
这里的遍历顺序和二维dp数组不同。二维dp遍历的时候,背包容量是从小到大,而一维dp遍历的时候,背包是从大到小。这是因为一维dp,本质上还是在遍历一个二维数组,只不过是在不断地对价值进行更新,如果我们从小到大进行遍历,就可能会导致一个物品被多次放入。
另外,对于两层for循环,代码中是先遍历物品嵌套遍历背包容量,那可不可以先遍历背包容量嵌套遍历物品呢?其实是不可以的,因为一维dp的写法,背包容量一定是要倒序遍历(原因上面已经讲了),如果遍历背包容量放在上一层,那么每个dp[j]就只会放入一个物品,即:背包里只放入了一个物品。
- 举例推导dp数组
一维dp,分别用物品0,物品1,物品2 来遍历背包,最终得到结果如下:
C++代码
// 一维dp数组实现
#include <iostream>
#include <vector>
using namespace std;int main() {// 读取 M 和 Nint M, N;cin >> M >> N;vector<int> costs(M);vector<int> values(M);for (int i = 0; i < M; i++) {cin >> costs[i];}for (int j = 0; j < M; j++) {cin >> values[j];}// 创建一个动态规划数组dp,初始值为0vector<int> dp(N + 1, 0);// 外层循环遍历每个类型的研究材料for (int i = 0; i < M; ++i) {// 内层循环从 N 空间逐渐减少到当前研究材料所占空间for (int j = N; j >= costs[i]; --j) {// 考虑当前研究材料选择和不选择的情况,选择最大值dp[j] = max(dp[j], dp[j - costs[i]] + values[i]);}}// 输出dp[N],即在给定 N 行李空间可以携带的研究材料最大价值cout << dp[N] << endl;return 0;
}
LeetCode:416. 分割等和子集
力扣代码链接
文字讲解:LeetCode:416. 分割等和子集
视频讲解:动态规划之背包问题,这个包能装满吗?
基本思路
看到这类题目和容易想到之前做过的---698.划分为k个相等的子集这类题目,可以采用回溯法进行暴力求解,但是使用回溯法的时间复杂度是指数级别的(即集合中的每个数都存在选择和不选择两种状态,因此时间复杂度为2^n),会超时,因此需要进行优化。这里可以使用01背包进行求解(每个元素只使用一次)。
动规五部曲分析如下:
- 确定dp数组以及下标的含义
可以使用一维dp,也可以使用二维dp,为了减小空间复杂度,这里使用一维dp数组。其中dp[j] 表示: 容量为j的背包,所背的物品价值最大可以为dp[j]。本题中每一个元素的数值既是重量,也是价值。
- 确定递推公式
本题中每一个元素的数值既是重量,也是价值。所以前面的递推公式中的weight[i]和value[i]都可以使用nums[i]进行替换。因此递推公式为:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
- dp数组如何初始化
从dp[j]的定义来看,首先dp[0]一定是0。如果题目给的价值都是正整数那么非0下标都初始化为0就可以了,如果题目给的价值有负数,那么非0下标就要初始化为负无穷。
- 确定遍历顺序
在一维dp数组的遍历顺序中提到,如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历!
// 开始 01背包
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]);}
}
- 举例推导dp数组
dp[j]的数值一定是小于等于j的。如果dp[j] == j 说明,集合中的子集总和正好可以凑成总和j,理解这一点很重要。
用例1,输入[1,5,11,5] 为例,如图:
最后dp[11] == 11,说明可以将这个数组分割成两个子集,使得两个子集的元素和相等。
C++代码
class Solution {
public:bool canPartition(vector<int>& nums) {int sum = 0;// dp[i]中的i表示背包内总和// 题目中说:每个数组中的元素不会超过 100,数组的大小不会超过 200// 总和不会大于20000,背包最大只需要其中一半,所以10001大小就可以了vector<int> dp(10001, 0);for (int i = 0; i < nums.size(); i++) {sum += nums[i];}// 也可以使用库函数一步求和// int sum = accumulate(nums.begin(), nums.end(), 0);if (sum % 2 == 1) return false;int target = sum / 2;// 开始 01背包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]);}}// 集合中的元素正好可以凑成总和targetif (dp[target] == target) return true;return false;}
};
相关文章:

DAY35|动态规划Part03|LeetCode:01背包问题 二维、01背包问题 一维、416. 分割等和子集
目录 01背包理论基础(一) 基本思路 C代码 01背包理论基础(二) 基本思路 C代码 LeetCode:416. 分割等和子集 基本思路 C代码 01背包理论基础(一) 题目链接:卡码网46. 携带研究材料 文字…...
创建空向量:std::vector<int> v,刚创建时大小为0
创建一个空的std::vector<int> v会在刚创建时具有大小(size)为0的特点。这意味着此时向量中没有任何元素,而且其容量(capacity)也返回0,表明还没有为这个向量分配任何内存空间3。换句话说,…...

VBA基础2
VBA基础2 sub过程语法对单元格进行赋值操作连续赋值不连续赋值 cells (行,列)行引用rows列引用 (columns)offset位移属性End属性(指定返回) 使用VBA编辑器需要用AltF11打开 或者VB编辑器打开 可…...

计算机网络-GRE基础实验二
前面我们学习了GRE隧道的建立以及通过静态路由指向的方式使得双方能够网络互联,但是通过静态路由可能比较麻烦,GRE支持组播、单播、广播因此可以在GRE隧道中运行动态路由协议使得网络配置更加灵活。 通过前面的动态路由协议的学习我们知道动态路由协议都…...
JSON 使用
JSON 使用 JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。它基于JavaScript编程语言的一个子集,但因其文本格式清晰简洁,被广泛用于数据交换和存储。本文将详细介绍JSON的使用方法,包括其语法、数据类型、如…...

Leetcode—1539. 第 k 个缺失的正整数【简单】
2024每日刷题(206) Leetcode—1539. 第 k 个缺失的正整数 C实现代码 class Solution { public:int findKthPositive(vector<int>& arr, int k) {int missing 1;int cur 1;int n arr.size();int missingCnt 0;int ptr 0;for(; missingCn…...
深入浅出:PHP 控制结构与循环语句
文章目录 引言控制结构条件判断if-else 语句elseif 语句嵌套的 if 语句三元运算符 switch 语句 循环语句for 循环遍历数组使用 range() while 循环基本用法无限循环循环控制语句break 语句continue 语句do-while 循环 综合案例参考资料 引言 PHP 是一种广泛用于服务器端开发的…...
深入解析 Loss 减少方式:mean和sum的区别及其在大语言模型中的应用 (中英双语)
深入解析 Loss 减少方式:mean 和 sum 的区别及其在大语言模型中的应用 在训练大语言模型(Large Language Models, LLM)时,损失函数(Loss Function)的处理方式对模型的性能和优化过程有显著影响。本文以 re…...
c++ auto
在C中,auto 是一种类型推导关键字,它允许编译器根据初始化表达式的类型自动推导变量的类型。自 C11 标准引入以来,auto 使得代码更加简洁,并且可以减少冗长的类型声明,尤其是在类型名称非常复杂或难以立即确定的情况下…...

python中的列表、元组、字典的介绍与使用
目录 一、区别介绍 1.使用场景以及区别图 2.详细介绍 列表 元组 字典 二、例子操作 (一)列表list 1.定义和初始化 2.访问元素(下标) 3.修改元素(下标) 4.添加元素(append、下标insert) 5.删除…...
深入浅出:PHP中的表单处理全解析
引言 在Web开发的世界里,表单是用户与服务器之间交互的重要桥梁。它们允许用户提交信息,并通过后端语言(如PHP)进行处理。本文将带你深入了解PHP中的表单处理,从基础的创建和提交到高级的安全措施和实用技巧ÿ…...

双绞线直连两台电脑的方法及遇到的问题
文章目录 前言一、步骤二、问题总结:问题1:遇到ping不通的问题。问题2:访问其他电脑上的共享文件时提示输入网络凭证问题3:局域网共享文件时提示“没有权限访问,请与网络管理员联系请求访问权限” 前言 办公室里有两台电脑,一台装了显卡用于…...

2024年认证杯SPSSPRO杯数学建模D题(第一阶段)AI绘画带来的挑战解题全过程文档及程序
2024年认证杯SPSSPRO杯数学建模 D题 AI绘画带来的挑战 原题再现: 2023 年开年,ChatGPT 作为一款聊天型AI工具,成为了超越疫情的热门词条;而在AI的另一个分支——绘图领域,一款名为Midjourney(MJÿ…...
Qt 设置QLineEdit控件placeholderText颜色
Qt 会根据QLineEdit控件显示文本的颜色自动设置placeholderText颜色,如果想自定义placeholderText颜色,可以通过以下方法。 在样式文件中增加以下设置: QLineEdit#lineEdit_userName, QLineEdit#lineEdit_password{border: none;padding: 6…...
麒麟 V10 系统(arm64/aarch64)离线安装 docker 和 docker-compose
前期准备 查看操作系统版本,跟本文标题核对一下 uname -a查看操作系统架构 uname -m下载离线包 下载 docker 离线包 地址:https://download.docker.com/linux/static/stable/ 选择系统架构对应的文件目录:aarch64,我目前使用…...

Windows基线自动化检查脚本
本批处理脚本的主要目的是对Windows系统进行安全性检查。检查了多个安全参数和设置,以确保系统符合特定的安全标准。当然也可能有些检查项不是很准确,需要根据实际环境再调试一下,以下是该脚本的详细描述和功能分析: 1. 脚本初始…...

离谱的梯形滤波器——增加过渡点
增加过渡点 频率采样法(Frequency Sampling Method)是一种设计FIR滤波器的方法,通过在频域中指定希望的频率响应,然后利用逆离散傅里叶变换(IDFT)来获得滤波器的脉冲响应。然而,这种方法容易导…...
tauri下的两个常用rust web框架:Leptos和Trunk
tauri下有两个常用rust web框架,就是Leptos和Trunk Leptos Leptos 是一个基于 Rust 的 Web 框架。您可以在他们的官方网站上了解更多关于 Leptos 的信息。本指南适用于 Leptos 的 0.6 版本。 Leptos Leptos 是一个用 Rust 编写的现代、高效且安全的 Web 框架。它…...

pubmed关键词搜索技能1:待更新
1,白话变为领域内学术词: 例如,我想要做蛋白质糖基化修饰以功能,这个领域课题,则 第一性原理,首先是拆分词汇:糖基化(一般比蛋白质、修饰、功能要在title中更常见,或者是…...

【技巧】Mac上如何显示键盘和鼠标操作
在制作视频教程时,将键盘和鼠标的操作在屏幕上显示出来,会帮助观众更容易地理解。 推荐Mac上两款开源的小软件。 1. KeyCastr 这款工具从2009年至今一直在更新中。 https://github.com/keycastr/keycastr 安装的话,可以从Github上下载最…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...

【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...

DeepSeek源码深度解析 × 华为仓颉语言编程精粹——从MoE架构到全场景开发生态
前言 在人工智能技术飞速发展的今天,深度学习与大模型技术已成为推动行业变革的核心驱动力,而高效、灵活的开发工具与编程语言则为技术创新提供了重要支撑。本书以两大前沿技术领域为核心,系统性地呈现了两部深度技术著作的精华:…...