[刷题] 动态规划合集

题目1:剪绳子的乘积最大值

给定一根长度为n的绳子,请把绳子剪成m段(m、n都是整数,n>1并且m>1),每段绳子的长度记为k[0],k[1],…,k[m]。请问k[0]*k[1] * … *k[m]可能的最大乘积是多少?

例子:

例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。


1.定义状态方程:
f(n)=max⁡(f(i)×f(n−i)), i&lt;=n//2f(n) =\max (f(i)\times f(n-i)),\ i&lt;=n//2f(n)=max(f(i)×f(ni)), i<=n//2f(n)代表绳子长度为n时,所获得最大乘积. f(n)代表绳子长度为n时, 所获得最大乘积.f(n)n,.
从下而上计算: 先计算f(2)f(3)f(2) f(3)f(2)f(3) 再计算f(4)...f(n)f(4)...f(n)f(4)...f(n).

def maxproduct(n):
	if n < 2:
		return 0
	if n == 2:
		return 1
	if n == 3:
		return 2 
	dp = [ 0 for i in range(n+1)]
	dp[1] = 1 
	dp[2] = 2
	dp[3] = 3 
	   ❗️注意 这里dp数组中保存的与上面实际的输出不符合,
	    因为当长度为1,2,3,实际上不切的时候最大,但是不符合题目要求.
		但是当做递归的时候, 至少会切一刀.所以dp数组中保存他们原本的长度即可
	for i in range(4, n):
		maxproduct = 0
		for j in range(1,i//2 + 1):⭐️# i//2+1避免重复计算
			maxproduct = max(maxproduct, dp[j] * dp[i-j])
		dp[i] = maxproduct
	return dp[-1]
			

题目2:最长公共子串/子序列

最长公共子序列:
dp[i][j]dp[i][j]dp[i][j]
表示 aaa字符串的0 ~ iii的字符串与bbb字符串的0 ~ jjj的字符串的最长公共子序列


状态方程:
dp[i][j]={max⁡{dp[i−1][j],dp[i][j−1]},A[i−1]!=B[j−1]dp[i−1][j−1]+1,A[i−1]==B[j−1]dp[i][j] = \begin{cases} \max\{dp[i - 1][j],dp[i][j - 1]\} ,&amp; {A[i - 1] != B[j - 1]} \\ dp[i - 1][j - 1] + 1 , &amp; {A[i - 1] == B[j - 1]} \end{cases}dp[i][j]={max{dp[i1][j],dp[i][j1]},dp[i1][j1]+1,A[i1]!=B[j1]A[i1]==B[j1]

代码:

def LCS(a,b):
	dp = [[0 for _ in range(len(a)+1) ]for _ in range(2)]
	flag = 0
	for i in range(1,len(b)+1):
		for j in range(1,len(a)+1):
			if a[j-1] == b[i-1]:
				dp[1-flag][j] = dp[flag][j-1] + 1
			else:
				dp[1-flag][j] = max(dp[flag][j],dp[1-flag][j-1])
	return dp[-1][-1]

最长公共子串:
dp[i][j]dp[i][j]dp[i][j]
表示 以在aaa中以a[i]a[i]a[i]结尾的字符串与在bbb中以b[j]b[j]b[j]结尾的字符串的最长公共子串 .


状态方程:
dp[i][j]={0,A[i−1]!=B[j−1]dp[i−1][j−1]+1,A[i−1]==B[j−1]dp[i][j] = \begin{cases} 0 ,&amp; {A[i - 1] != B[j - 1]} \\ dp[i - 1][j - 1] + 1 , &amp; {A[i - 1] == B[j - 1]} \end{cases}dp[i][j]={0,dp[i1][j1]+1,A[i1]!=B[j1]A[i1]==B[j1]
代码:

def longestsustring(a,b):
    max_ = 0
    dp = [[0 for _ in range(len(b)+1)] for _ in range(2)]
    flag = 0
    for i in range(1,len(a)+1):
        for j in range(1,len(b)+1):

            if a[i-1] == b[j-1]:
                dp[1-flag][j] = dp[flag][j-1] + 1
                max_ = max(max_,dp[1-flag][j])
        flag = 1 - flag
    return max_

题目3: 正则匹配

Input:
s = “aab”
p = “cab”
Output: true
Explanation: c can be repeated 0 times, a can be repeated 1 time. Therefore it matches “aab”

Input:
s = “mississippi”
p = “misisp*.”
Output: false

dp[i][j]dp[i][j]dp[i][j] 表示p i位之前的字符串和s j位之前的字符串是否匹配.

状态方程:
dp[i][j]={s[j−1]==p[i−1] or p[i−1]==′.′,p[j−1]!=′∗′dp[i−1][j] or dp[i−2][j],p[j−1]==′∗′∣dp[i][j−1],p[j−1]==′∗′,p[i−2]==s[j−1]orp[i−2]==′.′dp[i][j] = \begin{cases} s[j-1]==p[i-1] \ or \ p[i-1]==&#x27;.&#x27; ,&amp; {p[j-1] != &#x27; * &#x27;} \\ dp[i-1][j] \ or \ dp[i-2][j] ,&amp;{p[j-1]==&#x27;*&#x27;}\\ | dp[i ][j-1] , &amp;{p[j-1]==&#x27;*&#x27;} ,&amp; p[i-2]==s[j-1] or p[i-2]==&#x27;.&#x27;\end{cases}dp[i][j]=s[j1]==p[i1] or p[i1]==.,dp[i1][j] or dp[i2][j],dp[i][j1],p[j1]!=p[j1]==p[j1]==,p[i2]==s[j1]orp[i2]==.

第一行: 当p[j−1]!=′∗′p[j-1] != &#x27; * &#x27;p[j1]!= 很简单, 只需要判断当前位的两个字符相不相同, 然后与之前的状态做与运算.
如图:
在这里插入图片描述
第二行:
p[j−1]==′∗′p[j-1] == &#x27; * &#x27;p[j1]==时, 当前元素是∗*, 考虑∗*的功效. 对∗*之前的元素, 可以取一次,也可以不取,可以取多次.
如果取一次的话, 那么就直接将dp[i−1][j]dp[i-1][j]dp[i1][j]的结果作为当前的结果.
如果一次也不取得话,再往上,忽略掉p中*之前的元素, 那么就是dp[i−2][j]dp[i-2][j]dp[i2][j]作为结果.
如下图纵向的传导:
在这里插入图片描述

还有一种功效, 就是*使得前面的元素取多次使得匹配:
有个前提, 就是p中∗*之前的元素p[i−2]p[i-2]p[i2]与当前s中的元素s[j−1]s[j-1]s[j1]相同了或者p[i−2]p[i-2]p[i2]干脆为. 才能考虑这种情况.
那么具体配不匹配是取决于dp[i][j−1]dp[i][j-1]dp[i][j1] 如上图, 横向的传导.

def isMatch(s,p):

    dp = [[False for _ in range(len(s)+1)] for _ in range(len(p)+1)]
    dp[0][0] = True

    for j in range(2,len(p)+1):
        dp[j][0] = dp[j-2][0] and p[j-1] == '*'

    for i in range(1,len(p)+1):
        for j in range(1,len(s)+1):

            if p[i-1] != '*':
                dp[i][j] = dp[i-1][j-1] and (p[i-1]==s[j-1] or p[i-1]== '.')
            else:
                dp[i][j] = dp[i-1][j] or dp[i-2][j]  

                if p[i-2] == s[j-1] or p[i-2] == '.':
                    dp[i][j] |= dp[i][j - 1]
    return dp[-1][-1]

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值