动态规划
最⻓公共⼦序列(Longest Common Subsequence,简称 LCS)
- 状态:两个指针i,j 从后往前遍历s1和s2
- dp数组:dp(i,j)或者dp[ i ][ j ]:对于s1[1…i]和s2[1…j],它们的LCS长度
- 选择:设s1和s2的最长公共子序列为lcs。那么对于s1和s2中的每个字符,要么在lcs中,要么不在
⽤两个指针 i 和 j 从后往前遍历 s1 和 s2 ,如果 s1[i]==s2[j] ,那么这个字符⼀定在 lcs 中;否则的话, s1[i] 和 s2[j] 这两个字符⾄少有⼀个不在 lcs 中,需要丢弃⼀个。
(对于 s1[i] 和 s2[j] 不相等的情况,两个字符都不在lcs中时:
dp[i][j] = max(dp[i-1][j],dp[i][j-1],dp[i-1][j-1]) ,原因在于我们对 dp 数组的定义,因为 dp[i-1][j-1] 永远是三者中最⼩的,max 根本不可能取到它,所以不考虑这种情况了) - base case:索引为 0 的⾏和列表⽰空串, dp[0][…] 和 dp[…][0] 都应该初始化为 0
dp(i,j)
= 0 if (i=-1||j=-1)
= dp(i-1,j-1) if(s[i]==s[j])
= max(
dp(i-1,j),dp(i,j-1)
) if(s[i]!=s[j])
对于字符串 s1 和 s2 ,⼀般来说都要构造⼀个这样的 DP table
为了⽅便理解此表,我们暂时认为索引是从 1 开始的,待会的代码中只要稍作调整即可。
dp[i][j] 的含义是:对于 s1[1…i] 和 s2[1…j] ,它们的 LCS ⻓度是 dp[i][j]
其中,d[2][4] 的含义就是:对于 “ac” 和 “babc” ,它们的LCS ⻓度是 2。
找状态转移⽅程的⽅法是,思考每个状态有哪些「选择」,只要我们能⽤正确的逻辑做出正确的选择,算法就能够正确运⾏。
class Solution(object):
def longestCommonSubsequence(self, text1, text2):
"""
:type text1: str
:type text2: str
:rtype: int
"""
m, n = len(text1),len(text2)
dp = [[0]*(n+1) for _ in range(m+1)]
for i in range(1, m+1):
for j in range(1, n+1):
if(text1[i-1] == text2[j-1]):
#找到lcs中的字符
dp[i][j] = 1 + dp[i-1][j-1]
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return dp[-1][-1]
注
- ⼦序列类型的问题,穷举出所有可能的结果都不容易,⽽动态规划算法做的就是穷举 +剪枝,它俩天⽣⼀对⼉。所以可以说只要涉及⼦序列问题,⼗有⼋九都需要动态规划来解决,往这⽅⾯考虑就对了
- dp table结构:m+1行,n+1列
dp = [[0]*(n+1) for _ in range(m+1)] - if(text1[i-1] == text2[j-1]):