动态规划问题:
0-1 背包问题
例如:背包的最大容量为:4,有三种商品重量和价值分别为:
wight = [2,1,3]
val= [4,2,3]
确定价值最大
解析:
第一步状态选择:
状态:背包的容量和可选的物品 选择:装or 不装
第二步定义dp数组:
把状态用数组表示: dp[i,w] 定义:对于前i个商品,当前背包为w时,背包可以装的数量
import numpy as np
N,W = 3,4
wight = [2,1,3]
val= [4,2,3]
#dp[i,w] 表示在背包剩余w的情况下,前i个商品的最大价值
dp = np.zeros((N+1,W+1))
def bag(val,wight,dp):
dp[0,:]= 0
dp[:,0]= 0
for i in range(1,N+1):
for w in range(1,W+1):
#如果剩余的背包值小于i个重量只能选择不装
if w < wight[i-1]:
dp[i,w]= dp[i-1,w]
else: #装不装择优
dp[i,w] = max(dp[i-1,w-wight[i-1]]+val[i-1],dp[i-1,w])
return dp[-1,-1]
bag(val,wight ,dp)
#[[0. 0. 0. 0. 0.]
# [0. 0. 0. 0. 0.]
# [0. 0. 0. 0. 0.]
# [0. 0. 0. 0. 0.]]
#[[0. 0. 0. 0. 0.]
# [0. 0. 4. 4. 4.]
# [0. 2. 4. 6. 6.]
# [0. 2. 4. 6. 6.]]
#在一个数组中,选择不相邻的数使和为最大
分析:
第一步:状态:可选的数字 选择:选 or 不选
第二步:定义dp[i] 表示前i个数字的最大和
注意边界条件
#递归-带有备忘录的递归,解决了重叠子问题
arr = [1,2,4,1,7,8,3]
memo = {}
def rec_opt(arr,i):
if i == 0:
return arr[0]
if i == 1:
return max(arr[0],arr[1])
if i in memo:
return memo[i]
else:
A = rec_opt(arr,i-2)+arr[i]
B = rec_opt(arr,i-1)
memo[i] = max(A,B)
return max(A,B)
rec_opt(arr,len(arr)-1)
#非递归
#[4,1,1,9,1]
#arr = [4,1,1,9,1]
arr = [1,2,4,1,7,8,3]
dp = [0 for _ in range(len(arr))]
def dp_opt(arr):
dp[0]=arr[0]
dp[1]= max(arr[0],arr[1])
for i in range(2,len(arr)):
dp[i] = max(dp[i-2]+arr[i],dp[i-1])
print(dp)
return dp[-1]
dp_opt(arr)
#[1, 2, 5, 5, 12, 13, 15]
判断一个数组里是否存在元素子集之和为S,如果存在返回True
分析:
第一步:状态:前i可选数 和 数字之和S 选择:选 or 不选
第二步:定义dp[i] [s]表示前i个数字是否可以凑出S值
注意边界条件
#判断一个数组里是否存在元素之和为S,如果存在返回True
import numpy as np
arr = [3,34,4,12,5,2]
def subsetdp(arr,S):
subset = np.zeros((len(arr),S+1),dtype= 'bool')
subset[:,0] = True
subset[0,:] = False
subset[0,arr[0]] = True
for i in range(1,len(arr)):
for s in range(1,S+1):
if s < arr[i]:
subset[i,s] = subset[i-1,s]
else:
A = subset[i-1,s]
B = subset[i-1,s-arr[i]]
subset[i,s] = A or B
return subset[-1,-1]
print(subsetdp(arr,9)) #True
print(subsetdp(arr,10)) #True
print(subsetdp(arr,11)) #True
print(subsetdp(arr,12)) #True
print(subsetdp(arr,13)) #False
最长公共子序列
最长公共子序列(Longest Common Subsequence,简称 LCS)是一道非常经典的面试题目,因为它的解法是典型的二维动态规划,大部分比较困难的字符串问题都和这个问题一个套路,比如说编辑距离。
题目就是让我们求两个字符串的 LCS 长度:
状态转移方程
输入: str1 = "abcde", str2 = "ace"
输出: 3
解释: 最长公共子序列是 "ace",它的长度是 3
分析:
第一步:状态:s1 和 s2 中的元素 选择:在 or 不在
第二步:定义dp[i][j]的含义是:对于s1[1…i]和s2[1…j],它们的 LCS 长度是dp[i][j]。
DP table:
def LongCommonSub(str1,str2):
row,col = len(str1),len(str2)
dp = [[0 for _ in range(col+1)] for _ in range(row+1)]
for i in range(1,row+1):
for j in range(1,col+1):
if str1[i-1] ==str2[j-1]:
dp[i][j] = dp[i-1][j-1] +1
else:
dp[i][j] = max(dp[i][j-1],dp[i-1][j])
return dp[-1][-1]
str1 = "abcde"
str2 = "ace"
res = LongCommonSub(str1,str2)
print(res) #3
求一个序列的回文,
在求“这个序列”和“这个序列的倒序列”的最长公共子序列。其中,回文不一定是连续的,最长公共子序列也不一定是连续的。
def LongCommonSub(str1,str2):
row,col = len(str1),len(str2)
dp = [[0 for _ in range(col+1)] for _ in range(row+1)]
for i in range(1,row+1):
for j in range(1,col+1):
if str1[i-1] ==str2[j-1]:
dp[i][j] = dp[i-1][j-1] +1
else:
dp[i][j] = max(dp[i][j-1],dp[i-1][j])
return dp[-1][-1]
import sys
if __name__ == '__main__':
while True:
line = sys.stdin.readline().strip()
lens = len(line)
if not line:
break
lcs = LongCommonSub(line,line[::-1])
print(lens-lcs)
最长公共子串(最长回文子串 )
动态规划状态转移方程式
class Solution:
def longestPalindrome(self, s: str) -> str:
'''
#动态规划
rever_s = s[::-1]
max_len,max_index = 0, 0
dp = [[0 for _ in range(len(s)+1)] for _ in range(len(s)+1)]
for i in range(1,len(s)+1):
for j in range(1,len(s)+1):
if rever_s[i-1] == s[j-1]:
dp[i][j] = dp[i-1][j-1]+1
if dp[i][j] >max_len:
#判断比较的字符是否来自同一个字符串
prei = len(s) - i
nowi = prei + dp[i][j]
if nowi == j:
max_len = dp[i][j]
max_index = j - max_len
else:
dp[i][j] = 0
return s[max_index:max_index+max_len]
'''
#中心扩展法:
max_len,max_index = 0,0
for i in range(len(s)):
left = self.expand(s,i,i)
right = self.expand(s,i,i+1)
max_ = max(left,right)
if max_>max_len:
max_len=max_
max_index = i-(max_len-1)//2
return s[max_index:max_index+max_len]
def expand(self,str1,left,right):
while left >=0 and right <len(str1) and str1[left] == str1[right]:
left -=1
right +=1
return right - left -1