【leetcode】01背包总结

news2024/12/29 13:52:45

01 背包

关键点

  • 容器容量固定
  • 每件物品只有两种状态:不选、选 1 件
  • 求最大价值

代码

int N, W; // N件物品,容量为W
int w[N], v[N]; // w为大小,v为容量

/* 数组定义 */
int[][] dp = new int[N][W + 1]; // 注意是W + 1, 因为重量会取到W
dp[i][j]; // 从下标为[0, i]的物品中选若干件物品(注意是若干件,不是全部),放入大小为j的容器时的最大价值

/* 递推公式 */
// 由于每件物品有选、不选两种状态,所以两种状态取最大值即可
// 比如,对于dp[i][j]来说,
// 如果不选下标为i的物品,那么价值 = dp[i - 1][j];
// 如果选下标为i的物品,那么价值 = dp[i - 1][j - w[i]] + v[i]
// 两种状态取max
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);

/* 初始化 */
// 从递推公式可以看出来,会存在dp[0][j]、dp[i][0]
// 对于dp[0][j]来说,回到数组定义,即选择下标为0的物品,放入大小为j的容器时的最大价值
// 这里又分两种情况,下标为0的物品的大小w[0]比j大,即放不进去,那么dp[0][j]=0;w[0]比j小,即放的进去,那么dp[0][j]=v[0]
// 对于dp[i][0]来说,回到数组定义,即从下标为[0, i]的物品中选若干件物品,放入大小为0的容器时的最大价值
// 显然dp[i][0] = 0
for(int i = 0; i < N; i ++ ) dp[i][0] = 0;
for(int j = 0; j <= W; j ++ ) {
	if (j >= w[0]) dp[0][j] = v[0];
    else dp[0][j] = 0;
}

/* 求解 */
// 先遍历物品、再遍历大小
// 由于dp[0]j]已经初始化过了,所以从第1件物品开始循环
// 由于dp[i][0]已经初始化过了,所以从重量为1开始循环
for(int i = 1; i < N; i ++ )
	for(int j = 1; j <= W; j ++ ) {
    	if (j < w[i]) dp[i][j] = dp[i - 1][j];
        else dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i];
    }

/* 输出答案 */
// dp[N - 1][W]: 即从[0, N-1]中选若干件物品,放入大小为W的容器时的最大价值
return dp[N - 1][W]; 

时间复杂度:O(NW)
空间复杂度:O(NW)

优化:滚动数组,二维空间 -> 一维空间

递推公式

滚动数组思想:
回到递推公式:
d p [ i ] [ j ] = M a t h . m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − w [ i ] ] + v [ i ] ) dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]) dp[i][j]=Math.max(dp[i1][j],dp[i1][jw[i]]+v[i])

可以看出,第一维只用到了相邻两层的一个状态,没有第三个状态了。那实际上这个维度就可以去掉了,相当于用一个变量表示两个状态,赋值前就是上一层的状态,赋值后就是下一层的状态!
所以递推公式变成:
d p [ j ] = M a t h . m a x ( d p [ j ] , d p [ j − w [ i ] ] + v [ i ] ) dp[j]=Math.max(dp[j],dp[j-w[i]]+v[i]) dp[j]=Math.max(dp[j],dp[jw[i]]+v[i])

容量 W 遍历方向

对于遍历方向也需要改。如下图所示:

可以看出,后面的状态是用到了前面的状态的。如果还是像二维一样从左往右遍历的话,会出现这种情况:dp[j-w[i]] 被物品 i 更新了,dp[j] 也被物品 i 更新了。由于 dp[j] 是由 dp[j-w[i]] 更新过来的,相当于在重量为 j 的容器里放了两次物品 i,这和 01 背包的题目含义就冲突了:每件物品只有选和不选两种状态。
所以就要保证 dp[j] 用到的 dp[j-w[i]] 是没有被更新过的。方法也容易,反过来遍历就可以了。

初始化

一维的初始化实际上就是初始化一个物品都没用到时的价值。显然,对于任何重量的容器,不放物品,价值就是 0。初始化代码:

for(int j = 0; j <= W; j ++ ) dp[j] = 0;

容量 W 和物品 N 的遍历顺序

对于二维的形式,两种遍历方式都可以,因为不管怎么样,dp[i][j] 都是被左上角的状态更新的,所以先更新左上角的哪一个,实际上都一样。但是对于一维的形式,情况就不一样了。详细看一下两种方法的遍历的含义:

  1. 先遍历容量 W,再遍历物品 N:(×)
for(int j = W; j >=0; j -- )
	for(int i = 0; i < N; i ++ ) {
    	if (j >= w[i]) dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]);
    }

这种方法,相当于是对于同一个重量 j,从 N 个物品中选 1 个,看哪个价值最大。那最后 dp[W] 表示的就是从 N 个物品中选 1 个物品时的最大价值,这显然与 01 背包的题目含义冲突了:从 N 个物品中选若干个物品时(每个物品只选一次)的最大价值。
进一步分析,为什么这种方法每次都只会用到一个物品?因为每次递推用到的 dp[j-w[i]] 都是 0,因为 j 是从大到小遍历的,递推公式相当于变成了 dp[j]=Math.max(dp[j], v[i]),那不就是 N 个物品取最大值吗?所以也进一步加深了对一维递推公式的理解:要利用一维的递推,dp[j-w[i]] 一定要叠加了前面选了物品的状态。

  1. 先遍历物品 N,再遍历容量 W:(√)
for(int i = 0; i < N; i ++ )
	for(int j = W; j >=0; j -- ) {
    	if (j >= w[i]) dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]);
    }

这种方法,是先考虑前面物品的使用情况,放到 dp 中去,然后再开始考虑下一件物品。所以这种方法中,dp[j - w[i]] 是叠加了前面选了若干件物品之后的状态的。
因此,应该选择第二种遍历顺序。

时空复杂度

时间复杂度:O(NW)
空间复杂度:O(W),空间上比二维少了一个数量级

完整代码

int N, W; // N件物品,容量为W
int w[N], v[N]; // w为大小,v为容量

/* 数组定义 */
int[] dp = new int[W + 1]; // 注意是W + 1, 因为重量会取到W
dp[j]; // 大小为j的容器时的最大价值

/* 递推公式 */
dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]);

/* 初始化 */
for(int j = 0; j <= W; j ++ ) dp[j] = 0;

/* 求解 */
// 每迭代一次i,dp就多叠加了一件物品的状态
for(int i = 0; i < N; i ++ )
	for(int j = W; j >= 0; j -- ) {
        if (j >= w[i]) dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]);
    }

/* 输出答案 */
// dp[W]: 大小为W的容器时的最大价值
return dp[W]; 

题目

https://kamacoder.com/problempage.php?pid=1046

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

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

相关文章

win11玩游戏找不到d3dx9如何解决?5种方法轻松搞定dll问题

在Windows 11操作系统环境下&#xff0c;如果用户在运行游戏时发现系统无法找到d3dx9组件&#xff0c;这一问题可能会引发一系列不良影响和限制&#xff0c;严重影响到用户的正常游戏体验。首先&#xff0c;由于d3dx9是DirectX 9.0c的一部分&#xff0c;负责处理图形渲染等关键…

Nodejs前端学习Day5

苦其心志&#xff0c;劳其筋骨 文章目录 前言一、处理路径问题二、path路径模块总结 前言 继续fs 一、处理路径问题 在使用fs模块操作文件时&#xff0c;如果提供的操作路径是以./或…/开头的相对路径时&#xff0c;很容易出现路径动态拼接错误的问题 原因&#xff1a;代码在…

选择合适的CRM管理系统,需要满足以下条件

随着数据时代的发展和企业业务的不断扩大&#xff0c;数据的比例开始增加&#xff0c;传统的数据计算方法不再适合现代企业。客户管理已成为企业最重要的组成部分之一&#xff0c;越来越多的企业开始关注客户管理。在crm管理系统上&#xff0c;企业希望通过crm管理系统&#xf…

搜索引擎Elasticsearch了解

1.Lucene 是什么? 2.模块介绍 Lucene是什么: 一种高性能,可伸缩的信息搜索(IR)库 在2000年开源,最初由鼎鼎大名的Doug Cutting开发 是基于Java实现的高性能的开源项目 Lucene采用了基于倒排表的设计原理,可以非常高效地实现文本查找,在底层采用了分段的存储模式,使它在读…

面试官:Mysql中EXISTS与IN的使用有哪些差异

在数据库查询优化中&#xff0c;查询效率直接关系到应用程序性能。其中&#xff0c;IN和EXISTS是两种常见的子查询操作符&#xff0c;广泛应用于SQL查询语句&#xff0c;但它们在执行效率上有所不同。 本文深入探讨IN和EXISTS的工作原理&#xff0c;以及在何种情境下选择更为合…

玩转WEB接口之三续篇【HTTPS证书申请 - nginx验证】

文章目录 一&#xff0c; 概述二&#xff0c;nginx下载三&#xff0c;访问域名1. 做域名映射2. 运行nginx并通过域名访问 四&#xff0c;配置SSL证书1. 配置证书文件2. nginx 添加证书文件 五、运行并验证1. 测试、重新加载2. https访问 一&#xff0c; 概述 接上篇 玩转WEB接…

Qt应用软件【串口篇】串口通信

文章目录 1.串口概述2.串口传输数据的基本原理电信号的传输过程 3.串口的几个概念数据位&#xff08;Data Bits&#xff09;奇偶校验位&#xff08;Parity Bit&#xff09;停止位&#xff08;Stop Bits&#xff09;流控制&#xff08;Flow Control&#xff09;波特率&#xff0…

找到满意的北京软件外包公司

寻得一家满意的软件外包开发公司&#xff0c;需明确自身需求&#xff0c;细心调研&#xff0c;筛选比较&#xff0c;这样方能找到技术实力雄厚、服务贴心的合作伙伴&#xff0c;助力企业数字化转型之路。要找到一家满意的软件外包开发公司&#xff0c;需要遵循以下几个步骤&…

UDF学习(七)非稳态宏和对流宏及UDS_DIFFUCITY宏

非稳态宏和对流宏—FLUENT UDF-DEFINE_UDS_UNSTEADY宏 非稳态如何挂载 UDF_DEFINE_UDS_FLUX宏 对流项的宏&#xff0c;可以从help文件中直接用 FLUENT UDF-DEFINE_UDS_DIFFUCITY宏 定义了扩散系数 两个宏&#xff1a;DEFINE_ANISOTROPIC_DIFFUSITY宏和DEFINE_DIFFUSIVITY&a…

校园圈子论坛系统--APP小程序H5,前后端源码交付,支持二开!uniAPP+PHP书写!

随着移动互联网的快速发展&#xff0c;校园社交成为了大学生们日常生活中重要的一部分。为了方便校园内学生的交流和互动&#xff0c;校园社交小程序逐渐走入人们的视野。本文将探讨校园社交小程序的开发以及其带来的益处。 校园社交小程序的开发涉及许多技术和设计方面。首先&…

用友移动管理系统 DownloadServlet 任意文件读取漏洞

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

MySQL:三大日志(binlog、redolog、undolog)

再了解三个日志前我们先了解一下MySQL的两层架构&#xff1a; Server 层负责建立连接、分析和执行 SQL。MySQL 大多数的核心功能模块都在这实现&#xff0c;主要包括连接器&#xff0c;查询缓存、解析器、预处理器、优化器、执行器等。另外&#xff0c;所有的内置函数和所有跨…

雅特力AT32 Workbench图形化代码生成工具,简化嵌入式开发利器

嵌入式系统应用市场广泛&#xff0c;早已遍及日常生活&#xff0c;随着产品需求复杂度的提升&#xff0c;32位MCU开发难度也随之增加&#xff0c;如何降低开发成本&#xff0c;缩短开发周期&#xff0c;是所有嵌入式开发人员的共同课题。 面对市场竞争日益加剧的情形&#xff…

JavaWeb创建详细

这个是自己总结的onenote 笔记 , 欢迎各位探讨指正

Lucene 查询原理

Lucene 查询原理 - 知乎 前言 Lucene 是一个基于 Java 的全文信息检索工具包&#xff0c;目前主流的搜索系统Elasticsearch和solr都是基于lucene的索引和搜索能力进行。想要理解搜索系统的实现原理&#xff0c;就需要深入lucene这一层&#xff0c;看看lucene是如何存储需要检…

移动端设计规范 - 文字使用规范

这是一篇关于移动端产品界面设计时&#xff0c;文字大小的使用规范&#xff0c;前端人员如果能了解一点的话&#xff0c;在实际开发中和设计沟通时&#xff0c;节省沟通成本&#xff0c;也能提高设计落地开发时的还原度。 关于 在做移动端产品设计时&#xff0c;有时候使用文字…

【JavaScript基础入门】06 JavaScript 数据类型

JavaScript 数据类型 目录 JavaScript 数据类型1. 数据类型1.1 数据类型简介1.2 简单数据类型1.2.1 简单数据类型&#xff08;基本数据类型&#xff09;1.2.2 数字型 N u m b e r \color{red}{Number} Number1.2.3 字符串型 S t r i n g \color{red}{String} String1.2.4 布尔…

介绍TCP/IP

TCP/IP&#xff08;传输控制协议/互联网协议&#xff09;是一种用于数据通信的基本通信协议&#xff0c;它是互联网的基础。TCP/IP指的是一组规则和过程&#xff0c;它规定了如何在网络上发送和接收数据。这个协议族由两个主要部分组成&#xff1a;传输控制协议&#xff08;TCP…

SpringBoot 源码解析 - 持续更新

开始 spring initilizer&#xff1a;根据依赖构建工具、springboot 版本等生成 Java 工程。手把手教你手写一个最简单的 Spring Boot Starter Starter 命名规则 Spring 官方定义的 Starter 通常命名遵循的格式为 spring-boot-starter-{name}&#xff0c;例如 spring-boot-star…