Datawhale 编程集训第七天
一、0-1 背包问题
问题描述
假设我们有n件物品,分别编号为1, 2…n。其中编号为 i
的物品价值为vi
,它的重量为wi
。为了简化问题,假定价值和重量都是整数值。现在,假设我们有一个背包,它能够承载的重量是W
。现在,我们希望往包里装这些物品,使得包里装的物品价值最大化,那么我们该如何来选择装的东西呢?问题结构如下图所示:
这个问题其实根据不同的情况可以归结为不同的解决方法。假定我们这里选取的物品每个都是独立的,不能选取部分。也就是说我们要么选取某个物品,要么不能选取,不能只选取一个物品的一部分。这种情况,我们称之为0-1背包问题。而如果我们可以使用部分的物品的话,这个问题则成为部分背包(fractional knapsack)问题。这里我们只考虑0-1背包问题。
解题思路: (实在不会,就在网上找了一个解决方案,粘贴如下)
选择n个元素中的若干个来形成最优解,假定为k个。那么对于这k个元素a1, a2, …ak来说,它们组成的物品组合必然满足总重量<=背包重量限制,而且它们的价值必然是最大的。假定ak是我们按照前面顺序放入的最后一个物品。它的重量为wk,它的价值为vk。
既然我们前面选择的这k个元素构成了最优选择,如果我们把第ak个物品拿走,对应于k-1个物品来说,它们所涵盖的重量范围为[0,(W-wk)]。W为背包允许承重的量。假定最终的价值是V,剩下的物品所构成的价值为V-vk。这剩下的k-1个元素则构成了一个W-wk的最优解。
(用反证法来推导。假定拿走ak这个物品后,剩下的这些物品没有构成W-wk重量范围的最佳价值选择。那么我们肯定有另外k-1个元素,他们在W-wk重量范围内构成的价值更大。如果这样的话,我们用这k-1个物品再加上第k个,他们构成的最终W重量范围内的价值就是最优的。这岂不是和我们前面假设的k个元素构成最佳矛盾了吗?所以我们可以肯定,在这k个元素里拿掉最后那个元素,前面剩下的元素依然构成一个最佳解。)
下面来求得这个最优解。
假定我们定义一个函数c[i, w],表示到第i个元素为止,在限制总重量为w的情况下我们所能选择到的最优解。那么这个最优解要么包含有i这个物品,要么不包含,肯定是这两种情况中的一种。如果我们选择了第i个物品,那么实际上这个最优解是c[i - 1, w-wi] + vi。而如果我们没有选择第i个物品,这个最优解是c[i-1, w]。这样,实际上对于到底要不要取第i个物品,我们只要比较这两种情况,哪个的结果值更大不就是最优的么?
还有一个情况我们需要考虑的就是,我们这个最优解是基于选择物品i时总重量还是在w范围内的,如果超出了呢?我们肯定不能选择它,这就和c[i-1, w]一样。(这里的wi指的是第i个物品的重量,而不是到第i个物品时的总重量。)
对于初始的情况,很明显c[0, w]里不管w是多少,肯定为0。因为它表示我们一个物品都不选择的情况。c[i, 0]也一样,当我们总重量限制为0时,肯定价值为0。
于是有:
我们可以定义两个数组 v, w。v[i]表示第i个物品的价值,w[i]表示第i个物品的重量。为了表示c[i, w],我们可以使用一个int[i][w]的矩阵。其中i的最大值为物品的数量,而w表示最大的重量限制。按照前面的递推关系,c[i][0]和c[0][w]都是0。而我们所要求的最终结果是c[n][w]。所以我们实际中创建的矩阵是(n + 1) 行,(w + 1)列。
import numpy as np
def solve(v,w,totalW,totalL):
resArr = np.zeros((totalL+1,totalW+1),dtype=np.int32)
for i in range(1,totalL+1):
for j in range(1,totalW+1):
if w[i] <= j:
resArr[i,j] = max(resArr[i-1,j-w[i]]+v[i],resArr[i-1,j])
else:
resArr[i,j] = resArr[i-1,j]
return resArr[-1,-1]
if __name__ == '__main__':
v = [0,60,100,120]
w = [0,10,20,30]
weight = 50
lenth = 3
result = solve(v,w,weight,lenth)
print(result)
二、Leetcode编程练习
题目:132 分割回文串II
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回符合要求的最少分割次数。
示例:
输入: "aab"
输出: 1
解释: 进行一次分割就可将 s 分割成 ["aa","b"] 这样两个回文子串。
思路:
抽象成一个DP问题,如果s[left:right]是回文并且s[:left]也是回文,那么s[:right]即是一个分割。同理如果dp[left:right]是最小回文分割并且dp[:left]也是最小回文分割,那么dp[:right]=dp[left:right]+dp[:left]。为了方便遍历,这里将dp[left:right]设置为1即s[left:right]是回文。为了减少运行时间,增加一个判断s[left:right]是否是回文序列的二维数组。
代码实现:
class Solution:
def minCut(self, s):
"""
:type s: str
:rtype: int
"""
ls=len(s)
dp=[0]*(ls+1)
dp[0]=-1
p=[[False]*ls for i in range(ls)]
for i in range(ls):
dp[i+1]=i
for i in range(ls):
for j in range(i+1):
if s[j]==s[i] and ((i-j<2) or p[j+1][i-1]):
p[j][i]=1
dp[i+1]=min(1+dp[j],dp[i+1])
return dp[ls]
运行结果:
参考内容:
https://www.jianshu.com/p/25f4a183ede5
https://blog.****.net/Neekity/article/details/84987098