1. 问题描述:
给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。如果不存在公共子序列 ,返回 0 。一个字符串的子序列是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的公共子序列是这两个字符串所共同拥有的子序列。
示例 1:
输入:text1 = "abcde", text2 = "ace"
输出:3
解释:最长公共子序列是 "ace" ,它的长度为 3 。
示例 2:
输入:text1 = "abc", text2 = "abc"
输出:3
解释:最长公共子序列是 "abc" ,它的长度为 3 。
示例 3:
输入:text1 = "abc", text2 = "def"
输出:0
解释:两个字符串没有公共子序列,返回 0 。
提示:
1 <= text1.length, text2.length <= 1000
text1 和 text2 仅由小写英文字符组成
来源:https://leetcode-cn.com/problems/longest-common-subsequence/
2. 思路分析:
① 最长公共子序列的问题是经典的二维动态规划问题,对于这样的问题我们最好能够熟悉其中的求解过程,当遇到类似的问题的时候那么就可以使用这种方法进行求解,而且最长公共子序列的问题一般都是固定的思路,下面是我对于这个问题的理解。
② 因为涉及到两个字符串之间的匹配,所以需要使用二维的列表进行解决(列表是python语言的数据结构,java或者c++可以使用数组),涉及到两个字符串之间的匹配所以比较容易想到的dp数组的含义是:dp[i][j]表示字符串text1中[0:i]位置与字符串text2中[0:j]位置中的最长公共子序列的长度,对于这种二维dp问题比较好的理解方式是画出简单例子的表格进行推导,画出表格之后会比较快速推导出dp数组之间的状态转移方程,力扣官方提供了一个例子的状态转移方程的求解过程(表格对于理解状态转移方程还是很有帮助的),很明显可以看到当text1[i] == text2[j]的时候那么就需要找到text1中的上一行上一列,text2中的上一行上一列进行对应的dp数组值dp[i - 1][j - 1]的基础上加1即可,表示text1与text2的上一个匹配的位置的dp列表值,当text1[i] != text2[j的时候那么肯定是找最靠近当前位置(i, j)的左边(i, j - 1)与上边位置(i - 1, j)找出其中的最大值即可,表示上一次text1与text2匹配的最长公共子序列的长度是多少,从题目中给出的例子的匹配过程会很清楚理解这个状态转移的过程。并且在一开始声明dp列表的时候行与列要多声明多一个长度,这样可以很方便地处理边界上的问题,其实根据二维dp列表的含义结合具体例子的表格推导还是比较容易理解其中的实现过程的。
③ 感觉对于二维dp问题的一个比较重要的点是需要画出表格的推导过程,这样会比较方便地推导出具体的状态转移方程
3. 代码如下:
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
s1, s2 = len(text1), len(text2)
# 声明一个行尾s1 + 1列为s2 + 1的二维列表
dp = [[0] * (s2 + 1) for i in range(s1 + 1)]
for i in range(1, s1 + 1):
for j in range(1, s2 + 1):
if text1[i - 1] == text2[j - 1]:
# 当字符相等的时候那么取出上一行上一列的dp列表值加1即可
dp[i][j] = dp[i - 1][j - 1] + 1
else:
# 取出左边或者是上面的dp列表值中的较大值表示当前dp[i][j]的最大值
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
return dp[s1][s2]