第一日
昨天做虾皮笔试,在做到编程题时,明明之前都有做过,但是一到现场,愣是做不出来,思路卡住就在那里罚做。我不愿再经历这样的痛苦时刻,于是打算每天刷题并进行算法分享。
1. 精确装满背包问题
题目要求:给定背包容量w,一行待选物品的重量c,要求选择的物品恰好能装满背包,同时输出具有最小物品数的方案。
注意:每个物品只能使用一次。
输入:
w = 10
c = [5, 2, 6, 4, 3]
输出:
[6, 4]
我的思路:
看到背包,想当然地用动态规划,我打算用自底向上的方法,先建立一个备忘录p(1*w)记录下当重量为某值时选择的最小物品数,然后从上到下依次记录。
在递归的函数内,分别计算选或者不选,然后将数量最小的方案返回。
我的naive代码
class Solution:
def trunkLoad(self, w, c):
# write code here
# 建立辅助数组g,g[i]为重量i时的最小装货个数
num = len(c)
g = [num for _ in range(num)]
p = [0 for _ in range(num)]
project = load(w,c,p,g)
print(p)
def load(w, c, p, g):
# 记方案为p,最后将最终结果写入方案
# 传入剩余重量w,剩余选择c
# 判断先前有无此方案
# 装载
# 判断是否能够容纳
if w - c[0]>=0:
# 查找先前是否有
if g[w-c[0]] != len(g):
y_number = g[w-c[0]]
else:
y_number = load(w-c[0],c[1:],p,g)
# 不装
n_number = load(w,c[1:],p,g)
# 比较谁的个数少
# 记录对应的方案
# 返回最小个数及其对应的方案
s = Solution()
w = 10
c = [5,2,6,4,3]
s.trunkLoad(w, c)
gpt答案
class Solution:
def trunkLoad(self, w, c):
n = len(c)
# 初始化物品选择数组,长度为 w+1,初始值为空列表
items = [[] for _ in range(w + 1)]
# 初始化物品数数组,长度为 w+1,初始值为无穷大
counts = [float('inf')] * (w + 1)
# 当背包重量为0时,不需要物品
counts[0] = 0
# 调用动态规划函数
self.dp_fill_knapsack(n, w, c, items, counts)
# 打印最小物品数和对应的方案
if counts[w] != float('inf'):
print("最小物品数:", counts[w])
print("对应的方案:", items[w])
else:
print("无法恰好装满背包")
def dp_fill_knapsack(self, n, w, c, items, counts):
for i in range(1, n + 1):
for j in range(w, c[i - 1] - 1, -1): # 从后向前遍历,防止物品重复使用
if counts[j - c[i - 1]] + 1 < counts[j]:
counts[j] = counts[j - c[i - 1]] + 1
items[j] = items[j - c[i - 1]] + [c[i - 1]]
# 示例
s = Solution()
w = 10
c = [5, 2, 6, 4, 3]
s.trunkLoad(w, c)
答案思路:
答案可以分为3部分:
-
所用变量的初始化:分别是在重量为w(数组下标)时最少数量的存储counts以及最少数量的解法items,注意这里是w+1,因为还要考虑重量为0的情况,就直接置为0了。
-
方法主体:我本来以为需要用迭代,但是这里直接用了for循环,自底向上地计算,相当于备忘录方法。
输入:1. 物品数量n,2. 背包重量w,3. 物品重量列表,4和5是前面初始化的备忘录
a.第一层循环:逐个考虑每个物品,并决定是否将其加入到某个特定重量的背包中
for i in range(1, n + 1):
b.第二层循环:遍历所有可能的重量,需要从后往前遍历,即从背包最大容量到刚好能装进当前物品容量的情况都要考虑
for j in range(w, c[i - 1] - 1, -1):
从后往前的原因:为了避免物品被重复遍历。
这里我详细给出自己对此的理解:因为所有的状态都要从容量较小的状态生成。

如上图所示,一开始除了0即重量为0时没有任何物品数外,其余的物品数量都是最大值,后面所有的状态更新都要基于位置0

第一次更新由0+当前物品重量实现了位置5的更新。试想,假如是从前往后,那么就应该是更新了位置5后,后面位置10的位置又会根据位置5再更新一遍,就无法实现物品的不重复选择。
这本质上是因为执行第二层循环的顺序是线性的,但是我们又希望这种更新是同步进行的,因此需要找出一种更新顺序,使得前面的更新不会影响到后面的更新情况,即所有更新都是在上一轮同一个起点进行的。而最后面的位置需要依赖前面所有的位置,为了保证其结果不受其它更新的影响,所以要首先对它进行更新。
c. 循环内部:
- 假如当前重量j的在考虑当前物品c[i - 1]重量的前置计数下counts[j - c[i - 1]]加上1后的计数小于当前重量下的原先计数,则执行更新。
- 更新计数:更新当前计数。
- 更新答案:在前一状态的答案中并上本物品重量并作为本状态的答案。
if counts[j - c[i - 1]] + 1 < counts[j]:
counts[j] = counts[j - c[i - 1]] + 1
items[j] = items[j - c[i - 1]] + [c[i - 1]]
- 答案输出:如果counts[w]不是空的,证明已经求解得满重量下的最小值,将对应的解法items[w]输出
解题心得:
- 倘若没有困于心衡于虑,则再好的答案对于内心也是没有作用,因为内心对答案并无需求
- 再多的问题,上手debug下看看变量在运行时的变化情况也就清楚了
- 善用gpt

浙公网安备 33010602011771号