状态的定义很关键+状态转移方程
动态规划包含三个重要的概念:最优子结构、边界、状态转移方程(思路反过来想,程序正过来写)
https://www.sohu.com/a/153858619_466939
一、背包问题
1、01背包问题
01背包的状态转换方程 f[i,j] = Max{ f[i-1,j-Wi]+Pi( j >= Wi ), f[i-1,j] }
f[i,j]表示在前i件物品中选择若干件放在承重为 j 的背包中,可以取得的最大价值。
Pi表示第i件物品的价值。
决策:为了背包中物品总价值最大化,第 i件物品应该放入背包中吗 ?
问题: 给定物品的重量weights=[1, 2, 5, 6, 7] ,对应的价值values=[1, 6, 18, 22, 28] , 背包能装的最大重量为capicity=11。问:我们用这个背包装什么物品能获得最大价值? 注意:每件物品只有一件。并且最终重量不能超过背包所能承载的重量。
参考:1、https://blog.csdn.net/mu399/article/details/7722810
2、http://www.pianshen.com/article/2073277310/
3、https://www.jianshu.com/p/25f4a183ede5
python的代码:
# 01 背包问题
def bag_01(weights, values, capicity):
n = len(values)
f = [[0 for j in range(capicity+1)] for i in range(n+1)]#建立一个二维矩阵
for i in range(1, n+1):
for j in range(1, capicity+1):
f[i][j] = f[i-1][j]
if j >= weights[i-1] and f[i][j] < f[i-1][j-weights[i-1]] + values[i-1]:
f[i][j] = f[i-1][j-weights[i-1]] + values[i-1]
return f
def show(capicity, weights, f):
n = len(weights)
print("最大价值:", f[n][capicity])
x = [False for i in range(n)]
j = capicity
for i in range(n, 0, -1):
if f[i][j] > f[i-1][j]:
x[i-1] = True
j -= weights[i-1]
print("背包中所装物品为:")
for i in range(n):
if x[i]:
print("第{}个".format(i+1))
if __name__ == '__main__':
# weights 指的是物品的重量
# values 指的是物品的价值
# capicity 指的是袋子能装的重量
n = 5
weights = [1, 2, 5, 6, 7]
values = [1, 6, 18, 22, 28]
capicity = 11
m = bag_01(weights, values, capicity)
# 打印矩阵
for i in range(len(m)):
print(m[i])
# 接下来输出要装的物品
show(capicity, weights, m)
3、复杂度优化
注意:用一维数组来定义状态方程的话,需要从后向前
def solve2(vlist,wlist,totalWeight,totalLength):
resArr = np.zeros((totalWeight)+1,dtype=np.int32)
for i in range(1,totalLength+1):
for j in range(totalWeight,0,-1):
if wlist[i] <= j:
resArr[j] = max(resArr[j],resArr[j-wlist[i]]+vlist[i])
return resArr[-1]
if __name__ == '__main__':
v = [0,60,100,120]
w = [0,10,20,30]
weight = 50
n = 3
result = solve2(v,w,weight,n)
print(result)
2、完全背包问题
转化为01 背包问题求解
01 背包问题是最基本的背包问题,我们可以考虑把完全背包问题转化为01 背包问题来解。
最简单的想法是,考虑到第i 种物品最多选⌊V /Ci⌋ 件,于是可以把第i 种物品转化为⌊V /Ci⌋ 件费用及价值均不变的物品,然后求解这个01 背包问题。这样的做法完全没有改进时间复杂度,但这种方法也指明了将完全背包问题转化为01 背包问题的思路:将一种物品拆成多件只能选0 件或1 件的01 背包中的物品。
为什么这个算法就可行呢?首先想想为什么01 背包中要按照v 递减的次序来循环。让v 递减是为了保证第i 次循环中的状态F[i; v] 是由状态F[i - 1, v - Ci] 递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i 件物品”这件策略时,依据的是一个绝无已经选入第i 件物品的子结果F[i -1, v - Ci]。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i 种物品”这种策略时,却正需要一个可能已选入第i 种物品的子结果F[i, v-Ci],所以就可以并且必须采用v递增的顺序循环。这就是这个简单的程序为何成立的道理。
值得一提的是,上面的伪代码中两层for 循环的次序可以颠倒。这个结论有可能会带来算法时间常数上的优化。
这个算法也可以由另外的思路得出。例如,将基本思路中求解F[i,v -Ci] 的状态转移方程显式地写出来,代入原方程中,会发现该方程可以等价地变形成这种形式:
3 多重背包问题
题目:有N 种物品和一个容量为V 的背包。第i 种物品最多有Mi 件可用,每件耗费的空间是Ci,价值是Wi。求解将哪些物品装入背包可使这些物品的耗费的空间总和不超过背包容量,且价值总和最大。
基本算法:
实战:
双袋购物
//对袋子1进行动态规划,resA[i]:一直使用袋子1到第i个点时的最大总价值
// //对袋子2进行动态规划,反向进行,resB[i]:一直使用袋子2从第i个点到最后的最大总价值
import sys
line1=sys.stdin.readline().strip()
values1=list(map(int,line1.split()))
n=values1[0]
A=values1[1]
B=values1[2]
v=[]
w=[]
for i in range(n):
line=sys.stdin.readline().strip()
values=list(map(int,line.split()))
v.append(values[1])
w.append(values[0])
out=0
f=[0 for j in range(A+1)]#包的重量
baga=[0 for i in range(n+1)]
for i in range(1,n+1):#前n个物品
for j in range(A,w[i-1]-1,-1):
f[j]=max(f[j],f[j-w[i-1]]+v[i-1])
baga[i]=f[A]
f1=[0 for j in range(B+1)]#包的重量
bagab=[0 for i in range(n+2)]
for i in range(n,0,-1):#后n个物品
for j in range(B,w[i-1]-1,-1):
f1[j]=max(f1[j],f1[j-w[i-1]]+v[i-1])
bagab[i]=f1[B]
for i in range(n+1):
out=max(out,baga[i]+bagab[i+1])
print(out)
二、最长回文子串
DP
dp[l, r] = (s[l] == s[r] and (r - l <= 2 or dp[l + 1, r - 1]))
class Solution:
def longestPalindrome(self, s: str) -> str:
if not s:
return s
len_str=len(s)
if len_str==1:
return s
dp=[[0 for _ in range(len_str)] for _ in range(len_str)]
max_len=1
max_str=s[0]
for right in range(1,len_str):
for left in range(right):
if s[left]==s[right] and (dp[left+1][right-1]==1 or (right-left)<=2):
dp[left][right]=1
len2=right-left+1
if len2>max_len:
max_len=len2
max_str=s[left:right+1]
return max_str
总结:注意边界问题,
2、注意DP方程更新的顺序是“从左到右,从上到小”