1、经典0-1背包问题

问题描述:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内选择物品使得物品的总价值最高。
回顾对于二维的0-1背包问题递推关系式:
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − w e i g h t [ i ] ] + v a l u e [ i ] ) dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i]) dp[i][j]=max(dp[i−1][j],dp[i−1][j−weight[i]]+value[i])
d p [ i ] [ j ] dp[i][j] dp[i][j]表示的是从0到i标号的物品中放入承重为j的背包中最大的价值总和。
首先,对于此公式的解释:每次选择只有两种情况,在物品 0 , 1 , . . . , i {0,1,...,i} 0,1,...,i中不选择和选择物品 i i i。对于边界初始化: d p [ 0 ] [ 0 ] = d p [ i ] [ 0 ] = 0 , d p [ 0 ] [ j ] = v a l u e [ j ] dp[0][0]=dp[i][0]=0, dp[0][j]=value[j] dp[0][0]=dp[i][0]=0,dp[0][j]=value[j]其他数组中的值全部置为0,这是因为每次更新都取最大值,且所以这些值都会被覆盖,所以初始化全0是没问题的。同时,遍历次序物品先还是背包先,顺序还是逆序都是可以得到正确结果的。

2、滚动数组

回顾我们在做斐波那契数列的时候,空间复杂度可以从O(N)降到O(1)(用两个位置去储存即可),这也是滚动数组的基本思想,做空间压缩。
从上面的递推关系我们可以发现, d p [ i ] [ j ] dp[i][j] dp[i][j]只和第一维为 i − 1 i-1 i−1的dp数组有关,所以可以将递推公式中的 i − 1 i-1 i−1替换为 i i i,将第 i − 1 i-1 i−1层的结果写入 i i i层中,即 d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i ] [ j − w e i g h t [ i ] + v a l u e [ i ] ) dp[i][j] = max(dp[i][j], dp[i][j-weight[i] + value[i]) dp[i][j]=max(dp[i][j],dp[i][j−weight[i]+value[i])这说明第一个维度只用存储一个数据,这就是可以二维压缩到一维的原因。 d p [ j ] dp[j] dp[j]表示的是容量大小为 j j j的背包能达到的最大价值和。对比二维的递推表达,我们有如下一维递推: d p [ j ] = m a x ( d p [ j ] , d p [ j − w e i g h t [ i ] ] + v a l u e [ i ] ) dp[j] = max(dp[j], dp[j-weight[i]] + value[i]) dp[j]=max(dp[j],dp[j−weight[i]]+value[i])不放物品 i i i为 d p [ j ] dp[j] dp[j],这是因为 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j]=dp[i-1][j] dp[i][j]=dp[i−1][j]只是将上一层的结果进行了拷贝;放了物品 i i i的结果和二维的类似可得。初始化,根据一维 d p dp dp数组的含义,显然 d p [ 0 ] = 0 dp[0]=0 dp[0]=0,同样这里的 d p [ i ] dp[i] dp[i]可以置为0。关于遍历次序的理解,这里以一个例子来说明:假设有标号0,1,2的三个物品weight分别为1,3,4;value分别为15,20,30。遍历的顺序这里采用的是先遍历物品再逆序遍历背包。首先说明这样遍历的可行性。

 for i in range(3):for j in range(4, weight[i] - 1, -1):dp[j] = max(dp[j], dp[j - weight[i]] + value[i])

注意到 i = 0 , j = 4 , d p [ 4 ] = v a l u e [ 0 ] = 15 = d p [ 0 ] [ 4 ] i = 0 , j = 3 , d p [ 3 ] = v a l u e [ 0 ] = 15 = d p [ 0 ] [ 3 ] . . . i = 1 , j = 4 , d p [ 4 ] = m a x ( v a l u e [ 1 ] + d p [ 4 − w e i g h t [ 1 ] ] , d p [ 4 ] ) = 35 = d p [ 1 ] [ 4 ] . . i=0,j=4,dp[4]=value[0]=15=dp[0][4]\\ i=0,j=3,dp[3]=value[0]=15=dp[0][3]\\...\\ i=1,j=4,dp[4]=max(value[1]+dp[4-weight[1]],dp[4])=35=dp[1][4]\\.. i=0,j=4,dp[4]=value[0]=15=dp[0][4]i=0,j=3,dp[3]=value[0]=15=dp[0][3]...i=1,j=4,dp[4]=max(value[1]+dp[4−weight[1]],dp[4])=35=dp[1][4]..
最后得到的每个 d p [ j ] = d p [ 2 ] [ j ] dp[j]=dp[2][j] dp[j]=dp[2][j]。而如果将背包承重遍历改变为顺序:

i = 0 , j = 0 , d p [ 0 ] = 0 = d p [ 0 ] [ 0 ] i = 0 , j = 1 , d p [ 1 ] = v a l u e [ 0 ] = 15 = d p [ 0 ] [ 1 ] i = 0 , j = 2 , d p [ 2 ] = m a x ( d p [ 1 ] + v a l u e [ 0 ] , d p [ 2 ] ) = 15 + 15 = 30 ≠ d p [ 0 ] [ 2 ] = 15 . . . i=0,j=0,dp[0]=0=dp[0][0]\\ i=0,j=1,dp[1]=value[0]=15=dp[0][1]\\ i=0,j=2,dp[2]=max(dp[1]+value[0],dp[2])=15+15=30\neq dp[0][2]=15\\... i=0,j=0,dp[0]=0=dp[0][0]i=0,j=1,dp[1]=value[0]=15=dp[0][1]i=0,j=2,dp[2]=max(dp[1]+value[0],dp[2])=15+15=30​=dp[0][2]=15...
可以看到 d p [ 2 ] dp[2] dp[2]的结果显然是不对的,物品0被放入了两次。这是因为计算dp[2]的时候又用到了更新之后的dp[1]。可以对照二维dp的情况,i=0的情况都进行了初始化且没有相互影响。说的更明白一点,比如 i = 1 , d p [ 3 ] i=1,dp[3] i=1,dp[3]的结果会用到 d p [ 2 ] = d p [ 0 ] [ 2 ] dp[2]=dp[0][2] dp[2]=dp[0][2],而 d p [ 2 ] dp[2] dp[2]在此之前已经更新为 d p [ 1 ] [ 2 ] dp[1][2] dp[1][2],这样计算的 d p [ 3 ] dp[3] dp[3]肯定不对了。同理,交换物品和背包承重的循环顺序仍然会计算错误。这一块最好还是实际画一遍dp数组遍历过程才会更清楚。
下面以一个实际的例子来看。

3、实例

力扣分隔等和子集: 给你一个只包含正整数的非空数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
https://leetcode.cn/problems/partition-equal-subset-sum/

1、问题分析:从数组中选出若干数字,使得数字总价值为sum/2。对比01背包问题,nums的元素相当于物品重量,sum相等于背包承重。
2、设置dp数组和下标含义: d [ i ] [ j ] d[i][j] d[i][j]表示否存在从下标 [ 0 , i ] [0,i] [0,i]中选取元素,使得和为恰好为 j j j的情况,存在则返回True。
3、递推关系式:
A、不选下标为i的元素: d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j]=dp[i-1][j] dp[i][j]=dp[i−1][j]
B、选择下标为i的元素,因为数组中元素都是正整数,其中还要分两类来看:1)如果 n u m s [ i ] = = j nums[i]==j nums[i]==j, d p [ i ] [ j ] = T r u e dp[i][j]=True dp[i][j]=True
2)如果 n u m s [ i ] < j nums[i]<j nums[i]<j, d p [ i ] [ j ] = d p [ i − 1 ] [ j − n u m s [ i ] ] dp[i][j]=dp[i-1][j-nums[i]] dp[i][j]=dp[i−1][j−nums[i]]
3)如果 n u m s [ i ] > j nums[i]>j nums[i]>j,归为第一类情况。
4、初始化: d p [ 0 ] [ 0 ] = F a l s e dp[0][0]=False dp[0][0]=False

class Solution:def canPartition(self, nums: List[int]) -> bool:# 判断特殊条件sum1 = sum(nums)if sum1%2 != 0 or len(nums) < 2 or sum1 < 2 * max(nums):return Falsesum1 = sum1 // 2n = len(nums)# 初始化dpdp = [[False for x in range(sum1+1)] for x in range(n)]dp[0][nums[0]] = True# 递推:for i in range(1,n):for j in range(1,sum1+1):if nums[i] == j:dp[i][j] = Trueelif nums[i] < j:dp[i][j] = dp[i-1][j-nums[i]] or dp[i-1][j]else:dp[i][j] = dp[i-1][j]   return dp[n-1][sum1]

注意到这个问题中, d p [ i ] dp[i] dp[i]层的结果只和 d p [ i − 1 ] dp[i-1] dp[i−1]的相关,所以依然可以压缩为一维dp数组。

class Solution:def canPartition(self, nums: List[int]) -> bool:# 判断特殊条件sum1 = sum(nums)if sum1%2 != 0 or len(nums) < 2 or sum1 < 2 * max(nums):return Falsesum1 = sum1 // 2n = len(nums)dp = [False for x in range(sum1+1)]for i in range(1,n):for j in range(sum1,0,-1):if nums[i] == j:dp[j] = Trueelif nums[i] < j:dp[j] = dp[j-nums[i]] or dp[j]else:dp[j] = dp[j]   return dp[sum1]

动态规划0-1背包问题滚动数组相关推荐

  1. 动态规划——0/1背包问题(全网最细+图文解析)

    ✨动态规划--0/1背包问题(全网最细+图文解析) 作者介绍:

  2. 动态规划 0-1背包问题 滚动数组

    定义 dp[j]是从物品0到i中挑选物品,放进容量为j的背包中的最大价值总和. 初始化 int dp[maxn]; memset(dp, sizeof(dp), -0x3f3f3f3f); 一维滚动数 ...

  3. 代码随想录算法训练营第42天 | 动态规划 part04 ● 背包问题二维● 背包问题滚动数组 一维 ● 416. 分割等和子集

    # 二维dp数组,01背包 1.确定dp数组以及下标的含义 dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少. 2.  gpt 解决我的困惑 3.  另外 ...

  4. 动态规划0—1背包问题

    动态规划0-1背包问题 Ø    问题描写叙述:    给定n种物品和一背包.物品i的重量是wi,其价值为vi,背包的容量为C.问应怎样选择装入背包的物品,使得装 入背包中物品的总价值最大? Ø   ...

  5. 信息学奥赛一本通1267:【例9.11】01背包问题(二维dp与滚动数组优化)

    [题目描述] 一个旅行者有一个最多能装 MM 公斤的背包,现在有 nn 件物品,它们的重量分别是W1,W2,...,WnW1,W2,...,Wn,它们的价值分别为C1,C2,...,CnC1,C2,. ...

  6. 背包问题动态规划matlab,01背包问题动态规划详解

    计算机算法分析考试:动态规划0-1背包问题,怎么算她说她没醉,却一直摇摇晃晃掉眼泪:你说你爱她,却从未想过给她一个家. 要考试了,老师给划重点有一题:动态规划0-1背包问题,怎么算. 怎么理问题描述: ...

  7. 关于滚动数组的一些初学随笔

    什么是滚动数组 简单来说,滚动数组就是一种具有短暂记忆力的数组,它会牺牲时间来节省空间,用size=3的数组来"存储"30000个数据.这么说有点离谱.抽象,毕竟a[3]怎么存储a ...

  8. 终于结束的起点(滚动数组,记忆化搜索)

    任意门 终于结束的起点 题目背景 终于结束的起点 终于写下句点 终于我们告别 终于我们又回到原点 -- 一个个 OIer 的竞赛生涯总是从一场 NOIp 开始,大多也在一场 NOIp 中结束,好似一次 ...

  9. 【LeetCode笔记】494. 目标和(Java、动态规划、背包问题、滚动数组)

    文章目录 题目描述 思路 && 代码 1. 动态规划 O(n2n^2n2).O(n2n^2n2)(最方便理解,初版) 2. 转换成 01 背包问题 O(n2n^2n2).O(nnn) ...

最新文章

  1. 线段树之延时标记(区间修改)及lazy思想
  2. jsp通过易宝方式实现在线支付
  3. 详解Python类定义中的各种方法
  4. vue 扁平化_以vue+TreeSelect为例,如何将扁平数据转为tree形数据
  5. dubbo 视频教程
  6. ubuntu20.04安装微信
  7. 编码规约学习——《阿里巴巴 Java 开发手册》
  8. java广度优先爬虫示例_广度优先搜索与网络爬虫
  9. kettle 同步Oracle 与 Postgres
  10. C/C++播放音乐的函数的学习
  11. 云笔记Fusion Compute架构
  12. JSP 和 JavaBean 来实现一个简易计算器
  13. 集线器,交换机与路由器
  14. 计算机基础-将机械硬盘换成固态硬盘
  15. getElementsByName、getElementById的简单用法
  16. 连载:大学生求职七大昏招(十八)缺少职业素养(3)
  17. 正则表达式——匹配多个字符串之一
  18. 高效地分析Android内存--MAT工具解析
  19. InSAR基础:雷达回波信号的构成(什么是振幅?什么是相位?)
  20. 提取图片中的文字如何实现?两种方法均可操作!

热门文章

  1. 数据结构与算法——知识点总结
  2. tm影像辐射定标_Landsat TM 辐射定标和大气校正步骤
  3. 达梦数据库的静默安装
  4. @component(““)
  5. iftop查看系统网络状态命令详解
  6. python-王者荣耀皮肤爬取
  7. 微信小程序授权手机号解析错误
  8. 考研英语复习攻略:学长的各阶段备考建议
  9. 产品经理,考pmp证书含金量高还是考NPDP证书含金量高?
  10. 平面设计能不能自学成才?需要掌握什么知识?