当前位置: 首页 > news >正文

数据结构与算法:动态规划dp:背包问题:理论基础(状态压缩/滚动数组)和相关力扣题(416. 分割等和子集、1049.最后一块石头的重量Ⅱ、494.目标和)

背包问题

01背包理论基础

对于01背包问题,物品下标为0到i,对应的重量为weight[0]到weight[i],价值为value[0]到value[i],每个物品只可以取或不取,背包最大容量为j的场景。

常见的状态转移方程如下:
dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i])

其中,dp[i][j]的含义是:对于下标0到i的物品,任意取放,满足背包剩余最大容量为j的情况下能获得的最大价值。

那么dp[i][j]可以划分为两种情况

  • 取了下标为i的物品,其获得的最大价值为dp[i-1][j-weight[i]]+value[i]
  • 没有取下标为i的物品,其获得的最大价值为dp[i-1][j]

对于该数组的初始化,需要初始化i或j分别为0边界情况

  • i=0 的情况:如果 j >= weight[0],则我们可以取物品0,此时dp[0][j] = value[0]。否则,我们无法放入物品0,此时dp[0][j] = 0
  • j=0的情况:都为0,因为背包容量为0时,无法装入任何物品。

而对于i和j都非零时的情况,我们可以初始化成一个随意的值,因为根据状态转移方程来看,它的数组与它本身没有关系,所以在dp数组赋值时都会被覆盖掉。

接着我们思考它的遍历顺序
对于这种dp数组为二维数组的情况,两层for循环是可以颠倒位置的。因为无论是先遍历背包,还是先遍历物品,都能保障遍历dp[i][j]时,已经给dp[i-1][j]以及dp[i-1][j-weight[i]赋值了正确的数值了。

注意,对于所有01背包问题,都可以使用回溯算法来解决,对于每个物品取或不取,一共n个物品,那么算法的复杂度就是2的n次方。所以一般回溯会超时。

状态压缩(滚动数组)

而关于刚刚的场景,实际上我们还可以用一维数组来做,也就是常说的状态压缩,或者说滚动数组。

此时状态转移方程为dp[j]=max(dp[j],dp[j-weight[i]]+value[i])
其中,dp[j]的含义是:任意取放物品,满足背包 剩余最大容量为j 的情况下能获得的最大价值。

和二维的情况一样,dp[j]可以划分为两种情况

  • 取了下标为i的物品,其获得的最大价值为dp[j-weight[i]]+value[i]
  • 没有取下标为i的物品,其获得的最大价值为dp[j]

首先我们思考一个问题,为什么可以状态压缩?
这是因为从二维数组的状态转移方程,我们可以知道,dp[i]的数据只依赖dp[i-1]层的数据,譬如在计算dp[3]的时候,我们就不再需要存储dp[1]的数据了。这种只依赖于有限的数据的情况,我们都可以状态压缩。在这我们压缩成两行数据,一行是dp[i-1],一行是dp[i]

接着我们思考这个dp数组的初始化

  • j为0,代表对于下标0到i的物品,任意取放,背包最大容量为0时能获得的最大价值,毫无疑问,肯定都初始化为0
  • j非零,此时因为状态转移方程中我们给dp[j]赋值时,会比较其自身。所以应该初始化为一个负值

然后就是遍历顺序
虽然数组只有1层了,但是因为状态转移方程里还是有i存在的,所以遍历赋值还是有两层for循环的,外层是遍历物品,内层是遍历背包容量。而且必须先遍历物品,再遍历背包容量,且背包容量需要倒序遍历。

为什么背包容量需要倒序遍历呢?
如果正序遍历,dp[j - weight[i]]可能在同一轮更新后再次被使用,导致相当于选择了同一个物品多次,这就变成了完全背包问题。倒序遍历可以确保每个物品只被使用一次

416. 分割等和子集

在这道题虽然没有明说物品的价值,但实际上物品的价值就是等于它本身的重量。这样题目就转化为了,遍历下标从0到i的物品,任意拿取(物品只能拿取1次),满足重量为ans的情况?
为此我们想到了01背包,dp[j]表示遍历下标从0到i的物品,任意拿取,满足重量为j。当dp[j]==ans,也就是存在满足重量为ans的情况,那么返回True

代码1:最套板子的一集

class Solution:def canPartition(self, nums: List[int]) -> bool:n = len(nums)ans = 0for i in range(n):ans += nums[i]if ans%2:return Falseans=int(ans/2)dp = [0] * (ans+1) for i in range(n):for j in range(ans, nums[i]-1, -1):dp[j] = max(dp[j-nums[i]]+nums[i], dp[j])if dp[ans]==ans:return Trueelse:return False

最原始的一版,纯套板子。效率:2370ms,击败13.38%

代码2:优化外层for循环

根据上面的代码我们可以看到,其实我们不需要循环下标i,我们只需要拿到数本身就好了,所以直接用for num in nums

class Solution:def canPartition(self, nums: List[int]) -> bool:n = len(nums)ans = 0for i in range(n):ans += nums[i]if ans%2:return Falseans=int(ans/2)dp = [0] * (ans+1) for num in nums:for j in range(ans, num-1, -1):dp[j] = max(dp[j-num]+num, dp[j])if dp[ans]==ans:return Trueelse:return False

效率:2253ms,击败18.18%。

代码3:优化其他条件减少代码量

class Solution:def canPartition(self, nums: List[int]) -> bool:n = len(nums)ans = sum(nums) # 优化点1:用sum函数减少for循环if ans%2:return Falseans=ans//2 # 优化点2,用//替代int转化dp = [0] * (ans+1) for num in nums:for j in range(ans, num-1, -1):dp[j] = max(dp[j-num]+num, dp[j])if dp[ans]==ans:return Truereturn False

效率:2547ms,击败8.48%
可以看到其实效率没有说提升,但是代码量会少,看起来更简洁。

代码4:优化初始化条件(效率最高)

对于代码3,我们还可以再优化。因为我们其实并不关心dp[ans]的值具体是多少,也就是说我们并不在意最终能满足题意的方案数是多少,我们只关心能不能满足,那么初始化dp数组时就可以不用数值,而是用布尔值。注意这里,根据dp数组的含义,那么dp[0]的初始化就一定是True

class Solution:def canPartition(self, nums: List[int]) -> bool:n = len(nums)ans = sum(nums)if ans%2:return Falseans=ans//2dp = [False] * (ans+1) dp[0] = Truefor num in nums:for j in range(ans, num-1, -1):if dp[j - num]:dp[j] = Truereturn dp[ans]

效率:563ms,击败75.46%

1049.最后一块石头的重量Ⅱ

这道题转化一下思路,就可以变成和416. 分割等和子集一样的问题。
因为我们知道,如果两堆石头的重量越相近,那么相撞后可以留下的重量就越小。
也是求 任取其中的石块,看背包能装下的这堆石块的最大重量 是否能越靠近目标数(整体重量的一半)。

class Solution:def lastStoneWeightII(self, stones: List[int]) -> int:n = len(stones)all_sum = sum(stones)ans = all_sum//2dp = [0] * (ans+1)for stone in stones:for j in range(ans, stone-1, -1):dp[j] = max(dp[j], dp[j-stone]+stone)return abs((all_sum - dp[ans])-dp[ans])

效率:19ms,击败74.61%

494.目标和

这道题最难想的可能是不知道它和动态规划有什么关系。第一时间想的估计都是回溯去遍历每个元素取或不取。

但实际上,这道题可以将所有元素分为两类,一类是前面加+的,一类是前面加-的。那么就和1049.最后一块石头的重量Ⅱ非常类似。我们设正数集合的总和为pos,负数集合的总和为neg,那么存在以下逻辑:

pos+neg = sum
pos-neg = target

为此,推出pos = (target+sum)//2

也就是说,相当于我们要遍历所有物品,任意取放,找到满足背包容量为(target+sum)/2的情况数是多少。

所以,dp[j]的含义为,遍历前i个物品,任意取放,满足背包容量为j的情况数

那么首先关于边界条件的判断,就有两种:

  • pos不是整数。因为数组都是整数,所以pos也一定是整数。
  • pos小于0,根据pos的定义,应该是正数集合的总和,那么不可能小于0。

代码如下:

n = len(nums)
all_sum = sum(nums)if (target+all_sum)%2: # pos不是整数return 0pos= (target+all_sum)//2if pos< 0:# pos小于0return 0

其次,关于状态转移方程,dp[j]=dp[0]+dp[1]+dp[2]+...dp[j-1],也就是:
或者说dp[j]=dpj]+dp[j-nums[i]]或者说dp[j]+=dp[j-num]
在这里插入图片描述
为什么是累加?
因为每个物品都可以选择取或不取,所以对于每个j,它的值是由所有可能的前一个状态转移而来的。具体来说,dp[j]的值是由dp[j-1]、dp[j-2]、…、dp[0]这些状态转移而来的,因为这些状态都可能在加入当前物品后达到j。

最后关于初始化,在这我们要求的dp是情况数,那么dp[0]应该是多少呢?
实际上,dp[0]应该是1。举个例子:

nums = [0],target = 0
那么pos应该为1,因为给0前面加上一个+是一种情况。

最后的代码如下:

class Solution:def findTargetSumWays(self, nums: List[int], target: int) -> int:n = len(nums)all_sum = sum(nums)if (target+all_sum)%2:return 0pos = (target+all_sum)//2if pos < 0:return 0dp = [0] * (pos+1)dp[0] = 1for num in nums:for j in range(pos, num-1, -1):dp[j] += dp[j-num]return dp[pos]

效率:18ms,击败87.50%

总结

对于416. 分割等和子集,题目实际上为:给定一个重量为target的背包,是否能装满这个背包,转移方程为

dp[0] = True 
for num in nums:for j in range(target, num-1, -1):if dp[j - num]:dp[j] = True

对于1049.最后一块石头的重量Ⅱ,题目实际上为:给定一个重量为target的背包,该背包能装的最大价值(价值就是重量)是多少? 转移方程为:

dp = [0] * (target+1)
for stone in stones:for j in range(target, stone-1, -1):dp[j] = max(dp[j], dp[j-stone]+stone)

对于494.目标和,题目实际上为:给定一个重量为target的背包,求装满这个背包的方案数是多少? 转移方程为:

dp = [0] * (target+1)
dp[0] = 1
for num in nums:for j in range(target, num-1, -1):dp[j] += dp[j-num]

相关文章:

数据结构与算法:动态规划dp:背包问题:理论基础(状态压缩/滚动数组)和相关力扣题(416. 分割等和子集、1049.最后一块石头的重量Ⅱ、494.目标和)

背包问题 01背包理论基础 对于01背包问题&#xff0c;物品下标为0到i&#xff0c;对应的重量为weight[0]到weight[i]&#xff0c;价值为value[0]到value[i]&#xff0c;每个物品只可以取或不取&#xff0c;背包最大容量为j的场景。 常见的状态转移方程如下&#xff1a; dp[i…...

数字游牧时代:IT人力外包的范式革命与文明重构

当英国工业革命时期的企业主们将生产环节外包给家庭作坊时&#xff0c;他们不会想到这种生产组织方式会演变为21世纪最复杂的商业形态。IT人力外包行业在经历三十年爆炸式增长后&#xff0c;正在经历一场静默的范式革命。这场革命不仅重构着全球IT产业链的拓扑结构&#xff0c;…...

Qt - 地图相关 —— 3、Qt调用高德在线地图功能示例(附源码)

效果 作者其他相关文章链接:           Qt - 地图相关 —— 1、加载百度在线地图(附源码)           Qt - 地图相关 —— 2、Qt调用百度在线地图功能示例全集,包含线路规划、地铁线路查询等(附源码)           Qt - 地图相关 —— 3、Qt调用…...

cloudberry测试

一、引言 在当今大数据和 AI 飞速发展的时代&#xff0c;数据如同企业的核心资产&#xff0c;其价值不言而喻。数据库作为数据存储、管理和处理的关键工具&#xff0c;更是成为了各个领域的技术基石。无论是金融行业的交易记录处理&#xff0c;还是医疗领域的患者信息管理&…...

RocketMQ、RabbitMQ、Kafka 的底层实现、功能异同、应用场景及技术选型分析

1️⃣ 引言 在现代分布式系统架构中&#xff0c;&#x1f4e9;消息队列&#xff08;MQ&#xff09;是不可或缺的组件。它在系统&#x1f517;解耦、&#x1f4c9;流量削峰、⏳异步处理等方面发挥着重要作用。目前&#xff0c;主流的消息队列系统包括 &#x1f680;RocketMQ、&…...

UWB功耗大数据插桩调研

一、摘要 UWB功耗点 插桩点 日志关键字 电流 蓝牙持锁 BatteryStats的锁统计 vendor_bluetooth_lock 30~40mA 测距 UwbSessionManager.startRanging UwbSessionManager.stoptRanging 或接入fadiKey Uwb状态广播 "com.fadiui.dkservice.action.uwb.state.change&q…...

郭羽冲IOI2024参赛总结

非常荣幸能代表中国参加第 36 36 36 届国际信息学奥林匹克竞赛&#xff08; I O I 2024 IOI2024 IOI2024&#xff09;。感谢 C C F CCF CCF 为我们提供竞赛的平台&#xff0c;感谢随行的老师们一路上为我们提供的帮助与支持。 在每场比赛的前一个晚上&#xff0c;领队、副领…...

03:Spring之Web

一&#xff1a;Spring整合web环境 1&#xff1a;web的三大组件 Servlet&#xff1a;核心组件&#xff0c;负责处理请求和生成响应。 Filter&#xff1a;用于请求和响应的预处理和后处理&#xff0c;增强功能。 Listener&#xff1a;用于监听 Web 应用中的事件&#xff0c;实…...

lx-music落雪音乐-开源免费听歌软件[提供最新音源使用, 支持全网平台, 支持无损音乐下载]

lx-music_落雪音乐 链接&#xff1a;https://pan.xunlei.com/s/VOIpEt1xqf0un-vEQilidhjIA1?pwdgcux#...

129,【2】buuctf [BJDCTF2020]EzPHP

进入靶场 查看源代码 看到红框就知道对了 她下面那句话是编码后的&#xff0c;解码 1nD3x.php <?php // 高亮显示当前 PHP 文件的源代码&#xff0c;通常用于调试和展示代码结构 highlight_file(__FILE__); // 设置错误报告级别为 0&#xff0c;即不显示任何 PHP 错误信息…...

Python 面向对象(类,对象,方法,属性,魔术方法)

前言&#xff1a;在讲面向对象之前&#xff0c;我们先将面向过程和面向对象进行一个简单的分析比较&#xff0c;这样我们可以更好的理解与区分&#xff0c;然后我们在详细的讲解面向对象的优势。 面向过程&#xff08;Procedure-Oriented Programming&#xff0c;POP&#xff0…...

C语言之扫雷

C语言之扫雷 game.hgame.ctest.c 参考 https://blog.csdn.net/m0_62391199/article/details/124694375 game.h #pragma once #include <stdio.h> #include <time.h> #include <stdlib.h>#define ROW 9 #define COL 9#define ROWS ROW2 #define COLS COL2#de…...

半导体制造工艺讲解

目录 一、半导体制造工艺的概述 二、单晶硅片的制造 1.单晶硅的制造 2.晶棒的切割、研磨 3.晶棒的切片、倒角和打磨 4.晶圆的检测和清洗 三、晶圆制造 1.氧化与涂胶 2.光刻与显影 3.刻蚀与脱胶 4.掺杂与退火 5.薄膜沉积、金属化和晶圆减薄 6.MOSFET在晶圆表面的形…...

Ollama+DeepSeek R1+AnythingLLM训练自己的AI智能助手

1.下载Ollama安装 1.1.安装Ollama Ollama官网&#xff1a;Ollama 下载Ollama,点击“Download”按钮。 根据电脑操作系统&#xff0c;下载合适的版本即可。 下载完成后点击安装&#xff0c;完成后安装窗口会自动关闭&#xff0c;你的系统托盘图标会出现一个Ollama图标。 1.2.…...

基于java手机销售网站设计和实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…...

5-R循环

R 循环 ​ 有的时候&#xff0c;我们可能需要多次执行同一块代码。一般情况下&#xff0c;语句是按顺序执行的&#xff1a;函数中的第一个语句先执行&#xff0c;接着是第二个语句&#xff0c;依此类推。 编程语言提供了更为复杂执行路径的多种控制结构。 循环语句允许我们多…...

Qlabel 每五个一换行 并、号分割

学习点 Qlabel 每五个一换行 并、号分割 QString MainWindow::formatHobbies(const std::set<QString>& hobbies) {QString formattedHobbies;int count 0;for (const QString& hobby : hobbies) {if (count > 0 && count % 5 0)formattedHobbies…...

加速PyTorch模型训练:自动混合精度(AMP)

在深度学习领域&#xff0c;模型训练的速度和效率尤为重要。为了提升训练速度并减少显存占用&#xff08;较复杂的模型中&#xff09;&#xff0c;PyTorch自1.6版本起引入了自动混合精度&#xff08;Automatic Mixed Precision, AMP&#xff09;功能。 AMP简单介绍 是一种训练…...

【py】python安装教程(Windows系统,python3.13.2版本为例)

1.下载地址 官网&#xff1a;https://www.python.org/ 官网下载地址&#xff1a;https://www.python.org/downloads/ 2.64版本或者32位选择 【Stable Releases】&#xff1a;稳定发布版本&#xff0c;指的是已经测试过的版本&#xff0c;相对稳定。 【Pre-releases】&#…...

Django REST Framework:如何获取序列化后的ID

Django REST Framework&#xff1a;如何获取序列化后的ID &#x1f604; 嗨&#xff0c;小伙伴们&#xff01;今天我们来聊一聊Django REST Framework&#xff08;简称DRF&#xff09;中一个非常常见的操作&#xff1a;如何获取序列化后的ID。对于那些刚入门的朋友们&#xff…...

51c自动驾驶~合集58

我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留&#xff0c;CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制&#xff08;CCA-Attention&#xff09;&#xff0c;…...

Python:操作 Excel 折叠

💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...

聊聊 Pulsar:Producer 源码解析

一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台&#xff0c;以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中&#xff0c;Producer&#xff08;生产者&#xff09; 是连接客户端应用与消息队列的第一步。生产者…...

Opencv中的addweighted函数

一.addweighted函数作用 addweighted&#xff08;&#xff09;是OpenCV库中用于图像处理的函数&#xff0c;主要功能是将两个输入图像&#xff08;尺寸和类型相同&#xff09;按照指定的权重进行加权叠加&#xff08;图像融合&#xff09;&#xff0c;并添加一个标量值&#x…...

Leetcode 3577. Count the Number of Computer Unlocking Permutations

Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接&#xff1a;3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯&#xff0c;要想要能够将所有的电脑解锁&#x…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力

引言&#xff1a; 在人工智能快速发展的浪潮中&#xff0c;快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型&#xff08;LLM&#xff09;。该模型代表着该领域的重大突破&#xff0c;通过独特方式融合思考与非思考…...

1.3 VSCode安装与环境配置

进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件&#xff0c;然后打开终端&#xff0c;进入下载文件夹&#xff0c;键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...

VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP

编辑-虚拟网络编辑器-更改设置 选择桥接模式&#xff0c;然后找到相应的网卡&#xff08;可以查看自己本机的网络连接&#xff09; windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置&#xff0c;选择刚才配置的桥接模式 静态ip设置&#xff1a; 我用的ubuntu24桌…...

Selenium常用函数介绍

目录 一&#xff0c;元素定位 1.1 cssSeector 1.2 xpath 二&#xff0c;操作测试对象 三&#xff0c;窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四&#xff0c;弹窗 五&#xff0c;等待 六&#xff0c;导航 七&#xff0c;文件上传 …...

深入理解Optional:处理空指针异常

1. 使用Optional处理可能为空的集合 在Java开发中&#xff0c;集合判空是一个常见但容易出错的场景。传统方式虽然可行&#xff0c;但存在一些潜在问题&#xff1a; // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...