背包问题小结
介绍
学算法的时候,背包问题是一个很常见的动态规划问题,像什么01背包、完全背包、多重背包等,当时学的时候就有一些懵懵懂懂的,现在复习的时候又不会了,所以进行总结一下,方便日后查看学习。问题基本上都是lintcode上面的题目,然后在github上还找到一个专门讲背包问题的仓库:https://github.com/tianyicui/pack
背包问题
在n个物品中挑选若干物品装入背包,最多能装多满?假设背包的大小为m,每个物品的大小为A[i],题目地址:https://www.lintcode.com/problem/backpack/description
样例
1 | 样例 1: |
挑战
O(n x m) 的时间复杂度 and O(m) 空间复杂度
如果不知道如何优化空间O(n x m) 的空间复杂度也可以通过.
注意事项
你不可以将物品进行切割。
代码
1 | public class Solution { |
这是一个01背包问题,但是不是那种特别纯的01背包问题,这个背包没有价值,最终的目的是我们装的越多越好,所以我们可以把重量当作他的价值。01背包主要就是选择还是不选择这个物件的问题,在代码中我们先对物品的重量进行遍历,然后再对容量进行遍历,dp[j]表示当容量为j时,能够装得最满的量。对应的状态转移方程如下:
其实这个状态转移方程并不难得出来,我觉得难的是为什么使用一维数组是逆序遍历,而使用二维数组是正序遍历(当然二维数组逆序也可以)。
一维数组逆序遍历是因为如果采用正序遍历的时候,开始的遍历会影响到后续的遍历结果,就拿上面的样例1为例,当第一层遍历到最后一个物品,大小为5时,第二层循环从5-10遍历,当容量为5时,更新dp[5] = 5(这时5这个大小被使用),继续遍历到容量为10时,根据公式dp[10-5]+A[3]更新dp[10]=10(这时5这个大小又被使用),打破了只能使用一次的条件,所以采用从后向前遍历的方法,成功解决这个问题。
二维数组从前正序遍历,是因为dp[i] [j]使用的是上一轮遍历的结果,从下图的箭头也可以看出,这一轮的遍历根本不会对他产生影响。图来自https://www.cnblogs.com/mfrank/p/10533701.html这篇文章

背包问题 II
有 n 个物品和一个大小为 m 的背包. 给定数组 A 表示每个物品的大小和数组 V 表示每个物品的价值.
问最多能装入背包的总价值是多大?,题目地址:https://www.lintcode.com/problem/backpack-ii/description
样例
样例 1:
1 | 输入: m = 10, A = [2, 3, 5, 7], V = [1, 5, 2, 4] |
样例 2:
1 | 输入: m = 10, A = [2, 3, 8], V = [2, 5, 8] |
挑战
O(nm) 空间复杂度可以通过, 不过你可以尝试 O(m) 空间复杂度吗?
注意事项
A[i], V[i], n, m均为整数- 你不能将物品进行切分
- 你所挑选的要装入背包的物品的总大小不能超过
m - 每个物品只能取一次
代码
1 | public class Solution { |
这是一个01背包问题,和上题相比有了价值这个数组,但是思想一样,所以只需要一点小改动就解决了。
背包问题 III
描述
给出 n 个物品, 以及一个数组, nums[i]代表第i个物品的大小, 保证大小均为正数并且没有重复, 正整数 target 表示背包的大小, 找到能填满背包的方案数。
每一个物品可以使用无数次
样例
1 | 给出四个物品, 大小为 [2, 3, 5, 7], 价值为 [1, 5, 2, 4], 和一个大小为 10 的背包. 最大的价值为 15. |
代码
1 | package lintcode; |
这一题是一个完全背包问题,物品是无限多,因为这题是vip才能写的,所以我只能到网上搜这个题目自己写一下,需要注意的点是这里第二层循环采用正序遍历,因为物品时无穷多的。这一题还可以使用贪婪算法进行求解,就是找性价比最高的那个,我这里的实现方法是采用动态规划。
背包问题 IV
给出 n 个物品, 以及一个数组, nums[i]代表第i个物品的大小, 保证大小均为正数并且没有重复, 正整数 target 表示背包的大小, 找到能填满背包的方案数。每一个物品可以使用无数次
样例
样例1
1 | 输入: nums = [2,3,6,7] 和 target = 7 |
样例2
1 | 输入: nums = [2,3,4,5] 和 target = 7 |
代码
1 | public class Solution { |
这也是一个完全背包问题,但是又有点不同,是让我们找出填满背包的方案数,我们将dp设置为次数,然后发现这也是一个有最优子结构性质的问题。
背包问题 V
给出 n 个物品, 以及一个数组, nums[i] 代表第i个物品的大小, 保证大小均为正数, 正整数 target 表示背包的大小, 找到能填满背包的方案数。每一个物品只能使用一次
样例
给出候选物品集合 [1,2,3,3,7] 以及 target 7
1 | 结果的集合为: |
代码
1 | public class Solution { |
有了上面的经验我们只需要将背包问题IV的代码的第二层遍历改为逆序就解决了
总结
目前的问题就总结这么多,后面有再接着添加。