代码随想录算法训练营Day42 | 01背包理论基础 | 01背包 (滚动数组) | 416. 分割等和子集

文章目录

  • 01背包理论基础
    • 题目描述
    • 暴力解法
    • 动态规划
  • 01背包 (滚动数组)
    • 01背包总结
  • 416. 分割等和子集
    • 二维 dp
    • 一维 dp(滚动)
    • 题解

在这里插入图片描述

01背包理论基础

理论基础

题目描述

有 n 件物品和一个最多能背重量为w 的背包,已知第 i 件物品的重量是 weight[i],得到的价值是 value[i] 。每件物品只能用一次,求解将哪些物品装入背包里,可以使得物品价值总和最大。

暴力解法

本题太过于经典,以至于第一反应肯定都是动态规划。本题中,每个物品的状态就是“选”或“不选”,回溯算法可以很好地解决这样的组合问题。假设本题中的物品有 n 个,则叶子节点一共应该有 2 n 2^n 2n 个。毫无疑问,指数级别的复杂度肯定会超时。

动态规划

  1. dp 数组下标的含义:

    • dp[i][j]:给定下标为 [0, i] 的物品和重量为 j 的背包,能够获得的最大物品价值总和
  2. dp 递推公式:dp[i][j] = max(dp[i-1][j], dp[i-1][j - weight[i]] + value[i])

    • 给定了下标为 [0, i] 的物品,可以向前推一步
    • 如果不选择物品 i 能够获得更多的价值,则 dp[i][j] = dp[i-1][j]
      • 因为此时物品 i 没有被选择,可用的背包空间没有变化
    • 如果选择物品 i 能够获得更多的价值,则 dp[i][j] = dp[i-1][j - weight[i]] + value[i]
      • 因为此时物品 i 被选择了,可用的背包空间变少了,同时得到了物品 i 的价值
    • 由于不知道每一次状态转移时,应不应该选择当前物品,所以对这两种情况取一个最大值
  3. dp 数组的初始化:

    • 要明确二维 dp 数组的格式:对于每一个可能的背包重量都应该列举。例如,

      物品重量价值
      物品 0115
      物品 1310
      物品 2430

      此时只有三个物品,但所需要的重量应该是 [0, w] 中所有的非负整数(w 为给定的最大重量)。

    • 对于 dp[i][0],既然背包的最大重量是 0,什么都不能装,所以能获得的最大价值为 0

    • 对于 dp[0][j],当背包允许的重量 j >= weight[0] 时,能获得的最大价值就是 value[0],否则最大价值也为 0

    • 其余的值没有特定要求,因为 dp 的状态转移会直接赋值

  4. 遍历顺序:使用二维数组时,很显然有两个维度(物品价值和物品重量)

    • 根据 dp 的状态转移公式可知,(i, j) 计算需要左上角的矩形区域都先完成计算
    • 因此,先遍历价值和先遍历重量都能够解决本题
    • 注意,如果当前物品的重量超过了当前允许的背包承重 j,则直接进行状态转移 dp[i][j] = dp[i-1][j],否则会报错
  5. 举例推导 dp 数组:

def test_2_wei_bag_problem1(weight, value, bagweight):# 二维数组dp = [[0] * (bagweight + 1) for _ in range(len(weight))]# 初始化for j in range(weight[0], bagweight + 1):dp[0][j] = value[0]# weight数组的大小就是物品个数for i in range(1, len(weight)):  # 遍历物品for j in range(bagweight + 1):  # 遍历背包容量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])return dp[len(weight) - 1][bagweight]if __name__ == "__main__":weight = [1, 3, 4]value = [15, 20, 30]bagweight = 4result = test_2_wei_bag_problem1(weight, value, bagweight)print(result)

01背包 (滚动数组)

理论基础

在二维数组中,dp 公式为 dp[i][j] = max(dp[i-1][j], dp[i-1][j - weight[i]] + value[i]),我们认为当前 dp[i, j] 依赖于左上角的那个矩形区域。实际上,dp[i, j] 仅仅依赖于上一层的左侧。如下图,绿色区域的值只取决于黄色区域。

这样对上一层的依赖决定了我们其实没有必要保存整个矩阵,只需要保存一个一维数组即可。

  1. dp 数组下标的含义:
    • dp[j]:给定重量为 j 的背包,能够获得的最大物品价值总和
  2. dp 递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]),还是有两个选择
    • 选择放入当前的物品 i,得到 dp[j - weight[i]] + value[i]
    • 不选择放入当前物品 i,维持之前的 dp[j]
    • 相比于之前的二维 dp 公式同时省略了 [i-1],这也意味着在进行递推的时候,所使用的值都是没有经历过物品 i 的更新的(也就是上一层考虑过物品 i-1 之后的值)
  3. dp 数组的初始化:
    • 毫无疑问,dp[0] = 0。其余的值会在第一次更新之后被改写。由于涉及到了对当前值的比较中取 max,所以初始化的值不能影响 max 的结果,考虑到题目中的值都是正整数,初始化均为 0 即可。
  4. 遍历顺序:滚动数组的重点!
    	for i in range(len(weight)):  # 遍历物品for j in range(bagWeight, weight[i] - 1, -1):  # 遍历背包容量dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
    
    • 在遍历背包容量(对应二维数组的层遍历)时,是从后往前遍历的,这和二维数组很不一样。因为进行递推的时候,所使用的值都是没有经历过物品 i 的更新的,所以不能在针对当前物品 i 更新 dp[j]之前改变当前的 dp[k], k <= j 的值,只能后序遍历。
      • 如果进行前序遍历,只会试图重复加入物品(直接举例即可证明)
      • 而在二维数组解法中,并没有覆盖过上一层物品 i 的结果,所以无需采用倒叙遍历
    • 遍历顺序必须是先物品后容量。由于维持了一维数组,如果遍历顺序颠倒,dp[j] 就能记录背包容量为 j 时能够获得的一件物品的最大价值,因为初始值总是固定的。
  5. 举例推导 dp 数组:
def test_1_wei_bag_problem(weight, value, bagWeight):# 初始化dp = [0] * (bagWeight + 1)for i in range(len(weight)):  # 遍历物品for j in range(bagWeight, weight[i] - 1, -1):  # 遍历背包容量dp[j] = max(dp[j], dp[j - weight[i]] + value[i])return dp[bagWeight]if __name__ == "__main__":weight = [1, 3, 4]value = [15, 20, 30]bagweight = 4result = test_1_wei_bag_problem(weight, value, bagweight)print(result)

01背包总结

以上总结了两种01背包的解法:二维数组在清楚定义的情况下,更为简单明了,收到的限制也更少,但空间复杂度较高;一维滚动数组思路巧妙,需要特定的遍历顺序(内外层、倒序),但有超低的空间复杂度,代码简洁。

其中的难点(思维点)在于

  • 不同维度的遍历顺序
  • 同一纬度的前后遍历顺序
  • 不同的递推公式
  • 初始化逻辑

只有明白不同方法中以上难点的回答,才能真正理解01背包!

416. 分割等和子集

题目链接 | 理论基础

本题乍一看是典型的组合题:选取当前集合的子集,满足特定条件(和为一半)即可。经典的组合题自然应该想到回溯,然而回溯是暴力搜索(N叉树),拥有至少指数级的复杂度。当看到本题的数组长度,就该意识到回溯必然会超时。

从选取元素的角度看,每个元素最多只能使用一次,除了组合问题,也很符合01背包的设定。进行抽象后可以得到如下条件:

  • 背包的总重量为 sum(nums) // 2
  • 物品 i 的价值和重量均为 nums[i]
  • 背包需要正好装满
  • 每个物品 i 只能使用一次

二维 dp

  1. dp 数组下标的含义:dp[i][j] 的含义是,给定元素 nums[:i+1],背包容量为 j 时,能否正好装满当前背包
    • dp[i][j] 是 boolean
  2. dp 递推公式:dp[i][j] = dp[i-1][j] or dp[i-1][j-nums[i]]
    • 如果不选择当前元素 i,使用 nums[:i] 可以正好填满重量为 j 的背包,即是 dp[i-1][j]
    • 如果选择当前元素,使用 nums[:i] 可以正好填满重量为 j - nums[i] 的背包,即是 dp[i-1][j-nums[i]]
    • 这两种情况中,任意一种成立,则可以使用元素 nums[:i+1] 填满容量为 j 的背包;否则不可能
  3. dp 数组的初始化:在执行 dp 之前,先将 nums 进行了从小到大的排序
    • 为了不影响 or 的结果,所有值都先初始化为 False
    • 对于元素 nums[0]dp[0][nums[0]] = True
    • 对于重量 j=0 的背包,任何情况都能装满,所以 dp[i][0] = 0(这对之后取一个元素的情况很重要)
  4. dp 的遍历顺序:由于是二维数组,遍历顺序不那么重要,选择先遍历元素即可
  5. 举例推导:对于数组 [1, 5, 11, 5],dp 数组如下
    idx = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
    dp = [[T, T, F, F, F, F, F, F, F, F, F, F],[T, T, F, F, F, T, T, F, F, F, F, F],[T, T, F, F, F, T, T, F, F, F, T, T],[T, T, F, F, F, T, T, F, F, F, T, T]]
    
class Solution:def canPartition(self, nums: List[int]) -> bool:nums.sort()         # sort in advance makes things easierif sum(nums) % 2:return Falsetarget_sum = sum(nums) // 2# dp[i][j] represents whether nums[:i+1] can exactly make the sum of jdp = [[False] * (target_sum + 1) for _ in range(len(nums))] # initialize the dp for nums[0]if nums[0] <= target_sum:dp[0][nums[0]] = True# initialize the dp for sum j=0for i in range(len(nums)):dp[i][0] = True# dp formulafor i in range(1, len(nums)):for j in range(target_sum + 1):if j < nums[i]:dp[i][j] = dp[i-1][j]else:dp[i][j] = dp[i-1][j] or dp[i-1][j-nums[i]]return dp[-1][-1]

一维 dp(滚动)

根据二维 dp 数组压缩成的滚动数组,注意遍历顺序要先物品后重量,同时重量的遍历要后序进行(由于对 nums 进行了排序,所以重量的遍历结束点可以直接确定)。

class Solution:def canPartition(self, nums: List[int]) -> bool:nums.sort()         # sort in advance makes things easierif sum(nums) % 2:return Falsetarget_sum = sum(nums) // 2# dp[i][j] represents whether nums[:i+1] can exactly make the sum of j# initialization to be False, so that it does not make effect when operating "or"dp = [False] * (target_sum + 1)# initialize the dp for sum j=0dp[0] = True# dp formulafor i in range(len(nums)):          # traverse nums from left to rightfor j in range(target_sum, nums[i]-1, -1):         # traverse sum j from right to leftdp[j] = dp[j] or dp[j - nums[i]]return dp[-1]

题解

以上我的思路(二维 dp 和滚动数组是同一种思路,不同的实现)是“严格要求正好装满重量 j”的基础上进行的,所以 dp 的状态转移也是通过 boolean 操作,相当于对经典的 01 背包做了一个变种。实际上本题不需要进行这么大的改写。

以下是正常01背包的滚动数组解法。

  1. dp 数组下标的含义:dp[j] 的含义是,给定容量为 j 的背包,能够得到的最大物品价值
  2. dp 递推公式:dp[j] = max(dp[j], dp[j-nums[i]] + nums[j])
    • 如果不选择当前元素 i,即是 dp[j]
    • 如果选择当前元素,即是 dp[j-nums[i]] + nums[i]
  3. dp 数组的初始化:不对 nums 提前排序
    • dp[0] = 0
    • 剩余值的初始化不影响第一次遍历时的 max 操作即可
  4. dp 的遍历顺序:滚动数组,先物品(行)后重量(列)
class Solution:def canPartition(self, nums: List[int]) -> bool:if sum(nums) % 2:return Falsetarget_sum = sum(nums) // 2dp = [0] * (target_sum + 1)for i in range(len(nums)):              for j in range(target_sum, -1, -1):     if j >= nums[i]:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])return dp[-1] == target_sum

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

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

相关文章

hadoop大数据集群中更换磁盘,balance的速度缓慢问题(解决)

hadoop大数据集群中更换磁盘&#xff0c;balance的速度缓慢问题&#xff08;解决&#xff09; 看现象只有4个bloucks在执行的 调整参数&#xff1a; 增大配置参数&#xff0c;观察重新负载的速度 修改配置文件 hdfs-site.xml dfs.datanode.balance.max.concurrent.moves100 …

使用GoLand进行远程调试

对部署进行配置 在此配置远程服务器地址&#xff0c;映射&#xff0c;是否自动上传(更新)等 选择SFTP类型 选择上传 另外给自动上传选项打钩 此时在本地修改某个文件&#xff0c;远程机器相应目录的文件&#xff0c;也会被同步修改 对远程调试进行配置 远程机器需要安装delve 而…

数论基础(II)。

数论基础&#xff08;II&#xff09;TOC 数论按照研究的数据、方法、方向不同&#xff0c;通常可以分为玄数论、素数论、和数论。无限个数&#xff0c;真正用得到的只有数头&#xff1b;数头比较重要的关限是100&#xff0c;120&#xff0c;十万&#xff0c;百亿&#xff0c;&…

百度“AI智障”到AI智能体验之旅

目录 前言一、百度PLATO1.抬杠第一名2.听Ta瞎扯淡3.TA当场去世了4.智障与网友的高光时刻 二、文心一言1.设计测试用例2.随意发问3.手机端约会神器 三、体验总结&#xff1a;四、千帆大模型 前言 最近收到了文心一言3.5大模型的内测资格&#xff0c;正巧之前也体验过它的前身&q…

MySQL数据库——多表查询(2)-内连接、外连接

目录 内连接 查询语法 内连接演示 外连接 查询语法 外连接演示 内连接 内连接查询的是两张表交集的部分&#xff0c;返回A表和B表交集部分的数据。内连接分为两种形式&#xff1a;隐式内连接和显式内连接。 查询语法 隐式内连接 SELECT 字段列表 FROM 表1,表2 WHERE 条…

Redis 事务

1. 是什么 1. 官网 https://redis.io/dosc/manual/transactions/ 2. 可以一次执行多个命令&#xff0c;本质是一组命令的集合。一个事务中的所有命令都会序列化&#xff0c;按顺序地串行化执行而不会被其它命令插入&#xff0c;不许加塞 2. 能干啥 一个队列中&#xff0c;一次…

基于SSM的旅游管理系统jsp房源信息java源代码Mysql

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 基于SSM的旅游管理系统 系统有2权限&#xff1a;管理…

【数据结构与算法】—— 手撕红黑树

目录 &#xff08;一&#xff09;红黑树的定义 1、红黑树的引入 2、红黑树的概念 3、红黑树的性质 &#xff08;二&#xff09;红黑树的操作 1、红黑树节点的定义 2、红黑树的插入操作 1️⃣ 思路 2️⃣ 代码实现 3、红黑树的删除操作&#xff08;了解&#xff09; …

扩散模型实战(八):微调扩散模型

推荐阅读列表&#xff1a; 扩散模型实战&#xff08;一&#xff09;&#xff1a;基本原理介绍 扩散模型实战&#xff08;二&#xff09;&#xff1a;扩散模型的发展 扩散模型实战&#xff08;三&#xff09;&#xff1a;扩散模型的应用 扩散模型实战&#xff08;四&#xf…

yolov5和yolov7部署的研究

1.结论 onnx推理比torch快3倍, openvino比onnx快一丢丢。 | yolov7.pt 转 onnx python export.py --weights best_31.pt --grid --end2end --simplify --topk-all 10 --iou-thres 0.65 --conf-thres 0.65 --img-size 320 320 --max-wh 200可以看到yolov7的 onnx是包括nms…

【Unity小技巧】手戳一个简单易用的游戏UI框架(附源码)

文章目录 前言整套框架分为三大部分框架代码调用源码参考完结 前言 开发一款游戏美术成本是极其高昂的&#xff0c;以我们常见的宣传片CG为例&#xff0c;动辄就要成百上千万的价格&#xff0c;因此这种美术物料一般只会放在核心剧情节点&#xff0c;引爆舆论&#xff0c;做高…

MATLAB中符号变量的使用方法解析

简介 MATLAB中常常使用符号变量&#xff0c;这里定义符号变量的函数是syms 使用方法如下 syms x y z 其中&#xff0c;x、y、z 是符号变量&#xff0c;可以是任意字母、数字或下划线组合而成的字符串。 举例1&#xff1a; 代码 以下是一个简单的例子&#xff0c;演示如何…

WebSocket- 前端篇

官网代码 // 为了浏览器兼容websocketconst WebSocket window.WebSocket || window.MozWebSocket// 创建连接 this.socket new WebSocket(ws://xxx)// 连接成功this.socket.onopen (res)>{console.log(websocket 连接成功)this.socket.send(入参字段) // 传递的参数字段}…

强化自主可控,润开鸿发布基于RISC-V架构的开源鸿蒙终端新品

2023 RISC-V中国峰会于8月23日至25日在北京召开,峰会以“RISC-V生态共建”为主题,结合当下全球新形势,把握全球新时机,呈现RISC-V全球新观点、新趋势。本次大会邀请了RISC-V国际基金会、业界专家、企业代表及社区伙伴等共同探讨RISC-V发展趋势与机遇,吸引超过百余家业界企业、高…

出现ZooKeeper JMX enabled by default这种错误的解决方法

系列文章专栏 学习以来遇到的bug/问题专栏 文章目录 系列文章专栏 前言 一 问题描述 二 解决方法 2.1 可能的原因分析 2.2 小编的问题解决方法 First&#xff1a;检查/etc/profile里面zookeeper的环境变量配置 Second&#xff1a;检查 zookeeper/conf/zoo.cfg里面的d…

minikube mac 启动

系统信息如下 最开始使用的minikube是1.22.0版本&#xff0c;按照如下命令启动&#xff1a; minikube start --memory7851 --cpus4 --image-mirror-countrycn遇到了下面一些问题&#xff1a; 1、拉取coredns:v1.8.0镜像失败 Error response from daemon: manifest for regis…

Tensorflow调用训练好的yolov5模型进行推理

文章目录 1、安装TensorFlow-GPU版本1.2、验证是否安装正常 2、将训练好的pt文件转换成onnx文件2.2、什么是Onnx模型和Tensorflow模型2.1、将onnx文件转换成pb文件 1、安装TensorFlow-GPU版本 1、创建虚拟环境python3.8 conda create -n TF2.4 python3.82、进入虚拟环境 conda…

智安网络|探索物联网架构:构建连接物体与数字世界的桥梁

物联网是指通过互联网将各种物理设备与传感器连接在一起&#xff0c;实现相互通信和数据交换的网络系统。物联网架构是实现这一连接的基础和框架&#xff0c;它允许物体与数字世界之间的互动和协作。 一、物联网架构的概述 物联网架构是一种分层结构&#xff0c;它将物联网系…

python面试:使用cProfile剖析程序性能

我们需要安装tuna&#xff1a;pip install tuna 程序执行完毕后&#xff0c;我们会得到一个results.prof&#xff0c;在CMD中输入指令&#xff1a;“tuna results.prof”。 import time import cProfile import pstatsdef add(x, y):resulting_sum 0resulting_sum xresulti…

(Windows )本地连接远程服务器(Linux),免密码登录设置

在使用VScode连接远程服务器时&#xff0c;每次打开都要输入密码&#xff0c;以及使用ssh登录或其它方法登录&#xff0c;都要本地输入密码&#xff0c;这大大降低了使用感受&#xff0c;下面总结了免密码登录的方法&#xff0c;用起来巴适得很&#xff0c;起飞。 目录 PowerSh…