动态规划面试宝典
卢誉声
Autodesk 首席工程师
9629 人已学习
新⼈⾸单¥59
登录后,你可以任选2讲全文学习
课程目录
已完结/共 23 讲
动态规划面试宝典
15
15
1.0x
00:00/00:00
登录|注册

07|完全背包:深入理解背包问题

你好,我是卢誉声。
在上节课中,我们用动态规划解法,成功解决了动态规划领域中的 Hello World 问题。这个问题虽然比较初级,但却很有代表性,它比较全面地展示了动归解题的套路。
但光解决一个 0-1 背包问题显然不够过瘾。如果你觉得应用动态规划的解题套路还不太熟练,没关系。现在我们就趁热打铁,继续刨根问底,讨论背包问题。
首当其冲的就是完全背包问题。它仍然是动态规划领域的经典问题,但是比 0-1 背包问题要复杂一些。不过嘛,我们之前总结的解题套路还是比较具有普适性的,因此我们仍然可以将其套用在完全背包问题上。
在开始今天的课程前,请你思考这样一个问题:既然都是背包问题,那么完全背包跟 0-1 背包问题会如何影响状态转移方程呢?
你不妨带着这个问题,有针对性地学习今天的内容。

完全背包问题

我们先来看看完全背包问题的描述。
问题:给你一个可放总重量为 的背包和 个物品,对每个物品,有重量 和价值 两个属性,那么第 个物品的重量为 ,价值为 。现在让你用这个背包装物品,每种物品都可以选择任意多个,问这个背包最多能装的价值是多少?
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入探讨了完全背包问题及其解决方法。通过分析算法问题特征,文章确定了动态规划适合解决完全背包问题。在推导状态转移方程时,详细介绍了动态规划解题框架的应用,包括初始化状态、状态参数、决策和备忘录的使用。文章还提供了Java和C++的代码实现,并对时间复杂度进行了优化讨论。最后,通过改进状态转移方程,消除了重复计算,提出了优化后的状态转移方程。文章还介绍了如何优化动态规划的空间复杂度,通过滚动数组的方式将庞大的状态转移表优化成只有两行的数组。通过深入的分析和清晰的逻辑,帮助读者更好地理解了完全背包问题及其解题思路。文章还提出了如何优化动态规划的时间复杂度和空间复杂度的方法,为读者提供了进一步优化算法的思路。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《动态规划面试宝典》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(25)

  • 最新
  • 精选
  • Z
    完全背包空间优化,是否用一维数组就行了。 代码: int bag(int[] w, int[] v, int N, int W) { int[] dp = new int[W+1]; // 依次遍历给定物品 for (int tn = 1; tn < N + 1; tn++) { // 当前背包容量 for (int rw = 1; rw < W + 1; rw++) { if (w[tn] <= rw) { dp[rw] = Math.max(dp[rw], dp[rw-w[tn]] + v[tn]); } } } return dp[W]; }

    作者回复: 这个空间优化没有问题。

    2020-11-03
    10
  • norton/Dark
    滚动数组那描述太绕了,排班也不好对比。意思就是tn和tn-1交替使用0和1行吧,这个技巧没用过的人,可能不理解滚动数组是怎么滚的

    作者回复: 嗯,你的理解是正确的。意思是 tn 和 tn-1 交替使用数组的 0 行和 1 行。 补充一下背景,让跟多人能看到: 滚动数组的方法在朴素算法当中使用的较为广泛,比如说,读取一个超大文本文件的每一行这样的问题,我们就不希望一次性将整个文本文件加载进入内存,这是因为我们需要的可能只是整个文本文件当中的极个别信息:比如行数、包含某个特定字符的行等等。 一般,我们可以考虑使用求余的方法,来实现周而复始的复用有限的数组空间。

    2020-09-28
    5
  • Paul Shan
    思考题 每个物品有固定数目,这里递归还要加一个变量记录当前元素所剩的物品个数,这是一个三元递归的问题。 也可以转为为0-1背包问题,将每个物品的数目展开,看成是不同的物体,0-1背包的解法并没有限制所有物品都不同。

    作者回复: 如果你说的三元递归说的是状态转移参数,递归是状态转移的过程。 那么对于第一个问题这里其实不用再追加一个参数,因为其实多重背包相对于完全背包只是加入了对数量的限制,因此只需要在遍历物品数量计算DP[i][j]的最优解的时候加上数量作为限制即可,不需要在状态转移中再追加新的参数,增加空间复杂度。 对于你的第二个思路,的确可以直接将物品按照数量展开,直接把多重背包转化成0-1背包,这个思路朴素简单而且好用。

    2020-09-29
    3
  • 宋不肥
    老师讲的很好!但是老师的图实在是画的二义性很多啊比如dp(2,3 -0*3)+0.3*k ...等等的表达,一边设计到容量的变化,一边又是价值,dp在内外表示的意思就是不一样的,虽然看懂了,但这种图看的真的很难受啊

    作者回复: 恩,在图文以及前后文符号使用的一致性上的确可以做得更好。

    2020-10-30
    1
  • CD
    Math.max(dp[tn][rw], dp[tn][rw-w[tn]] + v[tn]) 请麻烦 好好解释一下 和之前的 Math.max(dp[tn-1][rw], dp[tn-1][rw-w[tn]] + v[tn]) 从你的画的图中也看不到 有重复计算的

    作者回复: 在第7课中,搜索这个关键字定位内容:“不知道你发现了没有,在改进后的代码中没有 k 参与计算了 ......” 这里就是对重复计算的重叠子问题的解释,这个图应该已经比较清晰了,比如求DP(3,5),需要求解DP(2,5)、DP(2,4)、DP(2,3)、DP(2,2)、DP(2,1)、DP(2,0),如果要求解DP(3,4)的时候需要求解DP(2,4)、DP(2,3)、DP(2,2)、DP(2,1)、DP(2,0),这里就有5个重叠子问题。这相当于我们求解DP(3,4)后再求解一个DP(2,5)即可。 图中每个框都包含了右侧问题的所有子问题,这是显而易见的。

    2020-10-24
    1
  • 子夜2104
    老师讲的太好了,让人看了,还想继续读下一篇。 我觉得0 1背包是跟前n-1个物品比较,完全背包是跟当前物品的前m-1次比较,在代码上的差异主要体现在是用dp[i-1][j]还是dp[i][j]。

    作者回复: 谢谢你! 另外,你的对于0-1背包问题的描述,完全正确。

    2020-10-11
    1
  • norton/Dark
    时间复杂度物品数量不好理解,有物品类型数量n和单个物品取k个,k平方是怎么出现的呢?少了一步骤,可能会让大多数人注意力断供。

    作者回复: 这里时间复杂度的确有问题,应该是O(k * v^2)。已更新。

    2020-09-28
    1
  • 李晓清
    把数量展开,就变成了0-1背包问题,如:w=[3,2,1],v=[5,3,2] ,数量n=[2,2,3], 展开之后:w=[3,3,2,2,1,1,1],v=[5,5,3,3,2,2,2],然后就可以用0-1背包算法解决。

    作者回复: 对的

    2023-07-20归属地:北京
  • 风清扬
    int dp[N+1][W+1]; // 创建备忘录 memset(dp, 0, sizeof(dp)); // 初始化状态 for (int i = 0; i < N + 1; i++) { dp[i][0] = 0; } for (int j = 0; j < W + 1; j++) { dp[0][j] = 0; } C++代码这里前面都memset了,这两个循环应该没必要了吧?

    作者回复: 这里主要是为了显式说明第一行、第一列初始化为0。

    2023-03-11归属地:广东
  • Geek_48cca1
    O(kv ^2)是指数级别的复杂度么 原文:虽然完全背包问题比 0-1 背包问题更复杂一些,但是,出现指数级别的复杂度可不是一件好事

    作者回复: O(KV^2)并不是指数级别的复杂度,这里原文有错误,已修正。

    2023-02-08归属地:广东
收起评论
显示
设置
留言
25
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部