【leetcode】01背包总结

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++(6) 继承

文章目录 继承1. 继承1.1 什么是继承1.2 C 继承方式1.2.1 基本案例1.2.2 继承权限组合1.2.3 继承中构造函数的说法1.2.4 继承中析构函数的执行顺序1.2.5 继承中变量名称冲突问题1.2.6 继承中函数【重写】 继承 1. 继承 1.1 什么是继承 面向对象程序设计中最重要的一个概念是继…

C语言——指针进阶(四)

目录 一.前言 二.指针和数组笔试题解析 2.1 二维数组 2.2 指针笔试题 三.全部代码 四.结语 一.前言 本文我们将迎来指针的结尾&#xff0c;包含了二维数组与指针的试题解析。码字不易&#xff0c;希望大家多多支持我呀&#xff01;&#xff08;三连&#xff0b;关注&…

网络基础二 session、cookie、token

HTTP协议以明文方式发送内容&#xff0c;不提供任何方式的数据加密&#xff0c;如果攻击者截取了Web浏览器和网站服务器之间的传输报文&#xff0c;就可以直接读懂其中的信息&#xff0c;因此&#xff0c;HTTP协议不适合传输一些敏感信息&#xff0c;比如&#xff1a;信用卡号、…

正则表达式 文本三剑客

一 正则表达式&#xff1a; 由一类特殊字符及文本字符所编写的模式&#xff0c;其中有些字符&#xff08;元字符&#xff09;不表示字符字面意义&#xff0c;而表示控制或通配的功能&#xff0c;类似于增强版的通配符功能&#xff0c;但与通配符不同&#xff0c;通配符功能是用…

Log4j2的Appenders配置详解

官方配置文档 https://logging.apache.org/log4j/2.x/manual/appenders.html#RollingFileAppender <Appenders> 常使用的类如下&#xff1a; org.apache.log4j.ConsoleAppender&#xff08;控制台&#xff09; org.apache.log4j.FileAppender&#xff08;文件&#xff…

【Go-zero】手把手带你在goland中创建api文件并设置高亮

【Go-zero】手把手带你在goland中创建api文件并设置高亮 大家好 我是寸铁&#x1f44a; 总结了一篇手把手带你在goland中创建api文件并设置高亮解决方案的文章✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 问题复盘 在使用go-zero 框架时&#xff0c;常常需要用到goctl 一键生成…

Netty源码二:服务端创建NioEventLoopGroup

示例 还是拿之前启动源码的示例&#xff0c;来分析NioEventLoopGroup源码 NioEventLoopGroup构造函数 这里能看到会调到父类的MultiThread EventLoopGroup的构造方法 MultiThreadEventLoopGroup 这里我们能看到&#xff0c;如果传入的线程数目为0&#xff0c;那么就会设置2倍…

Vue2 props组件通信-父子组件传值

一、父组件向子组件传值 1、流程图 2、父组件代码 <template><div class"app"><UserInfo:usernameusername:ageage:isSingleisSingle:carcar:hobbyhobby></UserInfo></div> </template><script> import UserInfo from .…

汽车网络安全管理体系框架与评价-汽车网络安全管理体系框架

R155《网络安全与网络安全管理系统》法规中明确指出 &#xff0c; 汽车制造商应完成 “汽车网络安全管理体系认证” &#xff08;简称&#xff1a; CSMS认证&#xff09;以及 “车辆型式审批&#xff02; 且CSMS认证&#xff0c;是车辆型式审批的前提条件。 虽然我国相关政策尚…

【网络基础】IP

IP协议报头 4位版本号(version): 指定IP协议的版本, 对于IPv4来说, 就是4.4位头部长度(header length): IP头部的长度是多少个32bit, 也就是 length * 4 的字节数. 4bit表示最大的数字是15, 因此IP头部最大长度是60字节. 8位服务类型(Type Of Service): 3位优先权字段(已经弃用…

C++笔记之作用域解析符::和命名空间、作用域的关系

C++笔记之作用域解析符::和命名空间、作用域的关系 —— 杭州 2024-01-26 code review 文章目录 C++笔记之作用域解析符::和命名空间、作用域的关系1.`命名空间`和`作用域`两个术语的联系和区别命名空间(Namespace)作用域(Scope)联系与区别2.`作用域解析符::`和`命名空间`…

Codeforces Round 785 C. Palindrome Basis

C. Palindrome Basis 题意 定义一个正整数 a a a 是回文的&#xff08;没有前导 0 0 0&#xff09;当且仅当&#xff1a; a a a 的十进制表示形式回文 给定一个正整数 n n n &#xff0c;求出将 n n n 拆分成若干个回文数之和的方案数 思路 这是一个经典模型&#xff0…

Mybatis2:基础操作

Mybatis1 入门 目录 1 基础操作 1.1 准备数据库表 1.2 删除 1.2.1 功能实现 1.2.2 日志输入 1.2.3.1 介绍 1.2.3.2 SQL注入 1.2.3.3 参数占位符 1.3新增 1.3.1 主键返回 1.4更新 1.5 查询 1.5.1 根据ID查询 1.5.2 数据封装 1.5.3参数名说明 1 基础操作 1.1 准备…

107基于51单片机的数字温度计设计[proteus仿真]

基于51单片机的自行车测速系统设计[proteus仿真] 温度检测系统这个题目算是课程设计和毕业设计中常见的题目了&#xff0c;本期是一个基于51单片机的数字温度计设计 需要的源文件和程序的小伙伴可以关注公众号【阿目分享嵌入式】&#xff0c;赞赏任意文章 2&#xffe5;&…

linux -- 内存管理 -- 页面分配器

linux内存管理 为什么要了解linux内存管理 分配并使用内存&#xff0c;是内核程序与驱动程序中非常重要的一环。内存分配函数都依赖于内核中一个非常复杂而重要的组件 - 内存管理。 linux驱动程序不可避免要与内核中的内存管理模块打交道。 linux内存管理可以总体上分为两大…

2024年最新MacBook苹果电脑安装JDK8、JDK11教程,配置环境变量 + 快速切换JDK版本

本帖发布日期&#xff1a;2024年01月26日&#xff0c;全网最新教程整理。 1、概述 本文主要为在MacBook苹果电脑系统下安装JDK及环境变量配置。 教程并非原创&#xff0c;摘抄自互联网&#xff0c;本人作为更新整理亲测。&#xff08;也算给自己记录一贴&#xff09; 本帖分…

数据结构【初阶】--排序(归并排序和基数排序)

目录 一.归并排序的非递归写法 1.思想应用 2.代码基本实现 (1)单趟归并逻辑 (2)多趟&#xff08;循环&#xff09;的控制条件 ① 迭代条件&#xff1a;i2*gap ② 结束条件&#xff1a;i(或i<n-2*gap)<> (3)代码展示 ① 单趟逻辑 ②整体逻辑 3.优化代码…

Springboot响应数据详解

功能接口 Controller下每一个暴露在外的方法都是一个功能接口 功能接口的请求路径是RequestMapping定义的路径&#xff0c;浏览器需要请求该功能则需要发出该路径下的请求。 RestController RestControllerControllerResponseBody(响应数据的注解) ResponseBody 类型&#…

找不到d3dcompiler 43.dll如何解决,5个方法轻松解决d3dcompiler 43.dll问题

计算机系统中d3dcompiler_43.dll文件的丢失可能会引发一系列显著的问题&#xff0c;这一动态链接库文件在Windows操作系统中扮演着至关重要的角色。它主要与Direct3D图形API相关&#xff0c;对于支持和运行使用Direct3D技术开发的各种应用程序和游戏至关重要。 一旦d3dcompiler…

RabbitMQ-如何保证消息不丢失

RabbitMQ常用于 异步发送&#xff0c;mysql&#xff0c;redis&#xff0c;es之间的数据同步 &#xff0c;分布式事务&#xff0c;削峰填谷等..... 在微服务中&#xff0c;rabbitmq是我们经常用到的消息中间件。它能够异步的在各个业务之中进行消息的接受和发送&#xff0c;那么…