【算法 | 背包专题】01背包(解题思路+题单)

前言

什么是背包问题?

背包问题是一种经典的组合优化问题,它的核心思想是在有限的资源(如背包的容量)下,如何选择物品以达到某种目标(如最大价值)的最优解。

背包问题可以分为几种类型,其中最常见的有:

  1. 0/1背包问题:每个物品只能选择放入或不放入背包,不能分割。
  2. 完全背包问题:每种物品可以选择无限个,但每件物品只能选择一次。
  3. 多重背包问题:每种物品可以选择多个,且没有数量限制。

解决方法也有很多,比如动态规划,回溯搜索、分支限界,贪心等。

背包问题在实际生活中有很多应用,如资源分配、项目投资组合、货物装载、课程安排等。通过解决背包问题,我们可以在有限的资源下做出最优的决策。

本节我们就来看01背包问题。

问题描述

01背包问题的描述如下:

  • 现在,我们有一个背包,它有一个固定的承载重量限制 W(背包容量有限)。
  • 同时,我们有一组物品,每个物品都有自己的重量weight[i]和价值value[i]
  • 我们需要从这组物品中选择一些物品放入背包,并且每件物品只能用一次
  • 问:在不超过背包承载重量的前提下,放入背包的物品总价值最大是多少

在01背包中,每个物品最多只能用一次。

问题求解

暴力

在这个经典的 01背包问题中,对于我们的决策,每一件物品只有两个状态:

  • 不选

使用回溯法,我们可以搜索出所有情况,但时间复杂度是 O ( 2 n ) O(2^n) O(2n) n n n表示物品数量),因此暴力的解法是通不过的,需要进一步优化。

对于01背包问题,最常用的求解方法是动态规划

dp表的含义

使用动态规划的解法,需要定义状态(明确dp表的含义)和状态转移方程,将问题分解为子问题,然后通过迭代的方式,从小问题开始,逐步解决大问题。

我们来看,在01背包问题中,状态是如何定义的:

  • dp[i][j]:在前i个物品中选取若干个,使得总重量不超过j的情况下,能够获得的最大价值。

明确好dp表的含义后,我们就可以进行状态转移的讨论以及编码。

状态转移

对于每个物品i以及当前的背包容量j,我们考虑两种情况(选或不选):

  • 如果不选取i个物品:
    • 那么背包中物品的总价值就是在前i-1个物品中选取若干个,使得总重量不超过j的情况下,能够获得的最大价值,
    • dp[i][j] = dp[i - 1][j]
  • 如果选取i个物品:
    • 背包中物品的总价值就是在前i-1个物品中选取若干个,使得总重量不超过j-weight[i]的情况下,能够获得的最大价值,再加上第i个物品的价值,
    • dp[i][j] = dp[i - 1][j - weight[i]] + value[i]

我们对每个物品的每种可能性进行考虑,从而找出在总重量不超过背包容量的前提下,能够获得的最大价值。这就是01背包问题的解题思路。

解题流程

定义好状态,以及转移方程后,我们就可以开始推了。从第1个物品开始,对于每个物品,遍历所有的背包容量,根据状态转移方程更新dp表。最后,dp[n][t]就是最大价值。

在使用动态规划时,初始化操作是很关键的一步。对于dp[0][j](没有物品时),无论背包容量是多少,最大价值都是0,因此初始化就都是0。

代码如下:

/*** 使用动态规划解决01背包问题* @param weight 物品的重量数组* @param value 物品的价值数组* @param W 背包的总容量* @return 能够获得的最大价值*/
public static int compute(int[] weight, int[] value, int W) {int n = value.length;	// 有n件物品// dp[i][j]表示在前i个物品中选取若干个,使得总重量不超过j的情况下,能够获得的最大价值int[][] dp = new int[n+1][W+1];// 遍历每一个物品for(int i = 1; i<=n; i++) {// 遍历每一种背包容量for(int j = 0; j<=W; j++) {// 不选取第i个物品dp[i][j] = dp[i-1][j];// 如果背包的剩余容量大于等于当前物品的重量,考虑选取第i个物品if(j >= weight[i]) {// 选取第i个物品,更新最大价值dp[i][j] = Math.max(dp[i][j], dp[i-1][j-weight[i]] + value[i]);}}}// 返回在前n个物品中选取若干个,使得总重量不超过W的情况下,能够获得的最大价值return dp[n][W];
}

空间压缩

在上述代码中,我们可以看到,每次更新dp[i][j]时,我们只用到了上一行的数据,即dp[i-1][j]dp[i-1][j-weight[i]]。这意味着我们并不需要保存所有的dp[i][j],只需要保存上一行的数据就足够了,因此,我们可以将二维dp表改进为一维,俗称空间压缩。

空间压缩的思路是,我们使用一个一维数组dp[j]来代替二维数组dp[i][j]dp[j]表示在当前考虑的物品中选取若干个,使得总重量不超过j的情况下,能够获得的最大价值。

需要注意的是,我们每次在更新dp[i][j]时,总是用到了上一行中的dp[i-1][j]dp[i-1][j-weight[i]],在二维表中可以形象理解为,我所处的位置,依赖于上方的格子,以及左上方的格子。因此,进行空间压缩更新dp[j]时,我们需要从后往前更新dp[j],这样可以逐渐更新当前的格子。

如果我们从前往后更新,那么在计算dp[j]时,dp[j-weights[i]]可能已经被更新过了,它表示的是当前行的状态,而不是上一行的状态。而我们需要的是上一行的状态,因此我们必须从后往前更新。

下面是空间压缩后的代码:

/*** 使用动态规划解决01背包问题(空间压缩版本)* @param weight 物品的重量数组* @param value 物品的价值数组* @param W 背包的总容量* @return 能够获得的最大价值*/
public static int compute(int[] weight, int[] value, int W) {int n = value.length;	// 有n件物品// dp[j]表示在当前考虑的物品中选取若干个,使得总重量不超过j的情况下,能够获得的最大价值int[] dp = new int[W+1];// 遍历每一个物品for(int i = 1; i<=n; i++) {// 从后往前更新dp[j]for(int j = W; j>=weight[i]; j--) {// 选取第i个物品,更新最大价值dp[j] = Math.max(dp[j], dp[j-weight[i]] + value[i]);}}// 返回在所有物品中选取若干个,使得总重量不超过W的情况下,能够获得的最大价值return dp[W];
}

这段代码的时间复杂度仍然是 O ( n ∗ W ) O(n*W) O(nW),但是空间复杂度降低到了 O ( W ) O(W) O(W),其中 n n n是物品的数量, W W W是背包的容量。

注意,我们后面会经常使用空间压缩的版本,因此需要吃透这份代码。

模板题 | 采药

我们来看一道洛谷上的模板题。

测试链接:P1048 [NOIP2005 普及组] 采药

题目描述

辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”

如果你是辰辰,你能完成这个任务吗?

输入格式

第一行有 2 2 2 个整数 T T T 1 ≤ T ≤ 1000 1 \le T \le 1000 1T1000)和 M M M 1 ≤ M ≤ 100 1 \le M \le 100 1M100),用一个空格隔开, T T T 代表总共能够用来采药的时间, M M M 代表山洞里的草药的数目。

接下来的 M M M 行每行包括两个在 1 1 1 100 100 100 之间(包括 1 1 1 100 100 100)的整数,分别表示采摘某株草药的时间和这株草药的价值。

输出格式

输出在规定的时间内可以采到的草药的最大总价值。

样例

样例输入

70 3
71 100
69 1
1 2

样例输出

3

提示

【数据范围】

  • 对于 30 % 30\% 30% 的数据, M ≤ 10 M \le 10 M10
  • 对于全部的数据, M ≤ 100 M \le 100 M100

【题目来源】

NOIP 2005 普及组第三题

解题

这道题就是标准的01背包问题,每种草药只能采摘一次,也就是说每种物品只能选择一次或者不选择,不能选择多次。

我们定义dp[i][j]为在前i种草药中选取若干种,使得总时间不超过j的情况下,能够获得的最大价值。对于第i种草药,我们可以选择采摘,也可以选择不采摘。如果我们选择采摘,那么我们需要在剩余的时间j-weight[i]中选择前i-1种草药,使得总价值最大;如果我们选择不采摘,那么我们需要在时间j中选择前i-1种草药,使得总价值最大。

我们取这两种情况的最大值,就是dp[i][j]的值。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;public class Main {static int N = 101; // 草药的最大数量static int W = 1001;// 总时间的最大值static int[] weight = new int[N]; // 每种草药的采摘时间static int[] value = new int[N]; // 每种草药的采摘价值static int n, w; // 分别表示草药的数量和总时间public static void main(String[] args) throws IOException {BufferedReader br = new BufferedReader(new InputStreamReader(System.in));StreamTokenizer in = new StreamTokenizer(br);PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));while (in.nextToken() != StreamTokenizer.TT_EOF) {w = (int) in.nval;in.nextToken();n = (int) in.nval;for (int i = 1; i <= n; i++) {in.nextToken();weight[i] = (int) in.nval;in.nextToken();value[i] = (int) in.nval;}out.println(compute2());}out.flush();out.close();br.close();}// 经典解法public static int compute1() {// dp[i][j]表示在前i个草药中选取若干个,使得总时间不超过j的情况下,能够获得的最大价值int[][] dp = new int[n + 1][w + 1];for (int i = 1; i <= n; i++) {for (int j = 0; j <= w; j++) {// 不要i号草药dp[i][j] = dp[i - 1][j];if (j - weight[i] >= 0) {// 要i号草药dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - weight[i]] + value[i]);}}}return dp[n][w];}// 空间压缩版本public static int compute2() { int[] dp = new int[w + 1];for (int i = 1; i <= n; i++) {for (int j = w; j >= weight[i]; j--) {dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);}}return dp[w];}}

力扣题单

链接题解
2915. 和为目标值的最长子序列的长度题解
416. 分割等和子集题解
494. 目标和题解
2787. 将一个数字表示成幂的和的方案数题解
1049. 最后一块石头的重量 II题解

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

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

相关文章

java之static详细总结

static也叫静态&#xff0c;可以修饰成员变量、成员方法。 成员变量 按照有无static分为两种&#xff1a; 类变量&#xff1a;static修饰&#xff0c;属于类&#xff0c;与类一起加载一次&#xff0c;在内存中只有一份&#xff0c;会被类的全部对象共享实例变量&#xff08;…

Elastic AI Assistant for Observability 和 Microsoft Azure OpenAI 入门

作者&#xff1a;来自 Elastic Jonathan Simon 最近&#xff0c;Elastic 宣布 AI 观测助手现已正式向所有 Elastic 用户开放。该 AI 观测助手为 Elastic 观测提供了一种新工具&#xff0c;提供了大型语言模型&#xff08;LLM&#xff09;连接的聊天和上下文洞察&#xff0c;以解…

WebView 后退键处理技巧:如何处理网页历史记录

引言 WebView 是移动应用开发中一个非常重要的组件&#xff0c;它允许开发者在应用内部直接展示网页内容&#xff0c;而无需跳转到外部浏览器。这种能力极大地丰富了移动应用的功能&#xff0c;为用户提供了更加完整的体验。以下是 WebView 在移动应用中的一些常见使用场景&am…

JavaWeb入门——Web前端概述及HTML,CSS语言基本使用

前言&#xff1a; java基础已经学完&#xff0c;开始学习javaWeb相关的内容&#xff0c;整理下笔记&#xff0c;打好基础&#xff0c;daydayup!!! Web Web&#xff1a;全球广域网&#xff0c;也称万维网&#xff08;www World Wide Web&#xff09;&#xff0c;能够通过浏览器访…

Hadoop MapReduce

MapReduce分为两个阶段&#xff0c;分为Map阶段和Reduce阶段&#xff0c;可以自定义map函数和reduce函数&#xff0c; map函数的输入是行在文件的字节偏移量&#xff0c;value是文件的一行数据。 reduce函数的输入是key和对应key的value组&#xff0c;然后reduce函数可以对这…

加州大学欧文分校英语基础语法专项课程01:Word Forms and Simple Present Tense 学习笔记

Word Forms and Simple Present Tense Course Certificate 本文是学习Coursera上 Word Forms and Simple Present Tense 这门课程的学习笔记。 文章目录 Word Forms and Simple Present TenseWeek 01: Introduction & BE VerbLearning Objectives Word FormsWord Forms (P…

彩虹易支付搭建教程

服务器环境 推荐使用宝塔、AMH、XP等面板一键部署服务器环境。 PHP版本&#xff1a;>7.1&#xff0c;推荐7.4或8.0 MySQL版本&#xff1a;5.6或5.7 伪静态配置 直接上传后访问即可完成安装&#xff01;创建好网站之后&#xff0c;需要配置伪静态才能正常发起支付。以下分…

Linux——gdb

gdb调试 (1)debug版本: 在编译阶段会加入某些调试信息; 调试信息是在编译的过程中加入到中间文件.o文件的; gcc -c main.c -g:生成包含调试信息的中间文件 gcc -o main main.o 一步执行:gcc -o main main.c -g (1) (2)release版本: 发行版本,没有调试信息; gcc默认生成relea…

C++ 【桥接模式】

简单介绍 桥接模式属于 结构型模式 | 可将一个大类或一系列紧密相关的类拆分 为抽象和实现两个独立的层次结构&#xff0c; 从而能在开发时分别使用。 聚合关系&#xff1a;两个类处于不同的层次&#xff0c;强调了一个整体/局部的关系,当汽车对象销毁时&#xff0c;轮胎对象…

psutil库(获取系统资源信息)

1、功能简介 psutil库是Python的一个第三方模块&#xff0c;它提供了丰富的接口来获取操作系统和系统硬件的信息。以下是psutil的一些主要功能&#xff1a; CPU信息获取&#xff1a;可以使用psutil来获取CPU的逻辑数量和物理核心数量。这有助于了解系统的处理能力。磁盘使用情…

基于单片机光伏太阳能跟踪系统设计

**单片机设计介绍&#xff0c;基于单片机光伏太阳能跟踪系统设计 文章目录 一 概要二、功能设计三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机光伏太阳能跟踪系统的设计&#xff0c;旨在通过单片机技术实现对光伏太阳能设备的自动跟踪&#xff0c;以提高太阳…

Go语言中测试和性能

1. 测试:软件开发最重要的方面 测试软件程序可能是软件开发人员能够做的最重要的事情。通过测试代码的功能,开发人员能够在很大程度上确定程序是有效的。另外,每次修改代码后,开发人员都可运行测试,确认没有引入Bug和衰退。通过测试软件,还能够让软件工程师确认程序按期望…

寄快递便宜啦!德邦、韵达、京东、圆通等八大品牌快递五折起!

低价服务&#xff0c;为你的快递需求保驾护航。 一、与全网主流快递合作&#xff0c;信赖与质量的共同见证 是一家整合快递、物流、及国际快递资源的综合快递服务平台&#xff0c;通过人工智能比价系统&#xff0c;为个人及企业客户提供市面上优惠的快递价格&#xff0c;目前…

Android视角看鸿蒙第十一课-鸿蒙的布局之层叠布局Stack

Android视角看鸿蒙第十一课-鸿蒙的布局之层叠布局 导读 在Android中我个人认为&#xff0c;最离不开的就是LinearLayout和FrameLayout了&#xff0c;RelativeLayout我都基本不用的。 所以我把层叠布局排在了第二位。 官方描述 如何定义层叠布局 Stack组件为容器组件&#x…

【正点原子探索者STM32F4】TFTLCD实验学习记录

【正点原子探索者STM32】LCD实验学习记录 硬件硬件连接软件设计变量类型定义LCD参数结构体LCD地址结构体 函数定义读写命令和数据简介6个基本函数坐标设置函数画点函数读点函数字符显示函数LCD初始化 小结参考 硬件 STM32F407、4.3寸LCD屏 硬件连接 LCD_BL(背光控制)对应 PB1…

OCP Java17 SE Developers 复习题11

答案 A, C, D, E. A method that declares an exception isnt required to throw one, making option A correct. Unchecked exceptions can be thrown in any method, making options C and E correct. Option D matches the exception type declared, so its also correct…

漂亮易用且功能强大的最酷的开源在线海报图片设计器:Poster-Design

Poster-Design&#xff1a;最酷的开源在线海报图片设计器&#xff0c;让您轻松创作&#xff0c;尽享设计之美与强大功能的完美结合&#xff01;- 精选真开源&#xff0c;释放新价值。 概览 Poster-Design 是一款高度评价的在线设计工具&#xff0c;专为用户提供便捷而高效的海…

C++:类与对象(一)

hello&#xff0c;各位小伙伴&#xff0c;本篇文章跟大家一起学习《C&#xff1a;类与对象&#xff08;一&#xff09;》&#xff0c;感谢大家对我上一篇的支持&#xff0c;如有什么问题&#xff0c;还请多多指教 &#xff01; 文章目录 面向对象和面向过程的区别1.类的引入2.…

9.手写JavaScript大数相加问题

一、核心思想 找到两个字符串中最长的长度&#xff0c;对两个字符串在头位置补0达到相等的长度&#xff0c;相加时注意进位和类型转换&#xff0c;特别考虑当相加到第一位是如果仍然有进位不要忽略。此外&#xff0c;js中允许使用的最大的数字为 console.log("最大数&qu…

2024 蓝桥打卡Day35

20240407蓝桥杯备赛 1、学习蓝桥云课省赛冲刺课 【3-搜索算法】【4-枚举与尺度法】2、学习蓝桥云课Java省赛无忧班 【1-语言基础】3、代码练习数字反转数字反转优化算法sort排序相关String字符串相关StringBuilder字符串相关HashSet相关 1、学习蓝桥云课省赛冲刺课 【3-搜索算法…