输出所有最长公共子序列
- 什么是最长公共子序列
- 过程讲解
- 完整程序代码(python)
什么是最长公共子序列
在力扣题库中的1143题有一道最长公共子序列,但是那个只是返回最长子序列的长度,而没有输出所有的最长子序列
通过上图中的举例我们可以看出子序列是可以不连续的但是前后关系是不能变的
过程讲解
链接: b站一个关于最长子秩序的讲解视频
我们假设两个序列 s1 = "abcbdab"
和s2 = "bdcabc"
去求它们两个的最长公共子序列
首先我们假定D[ i ][ j ]的值是s1前 i 个字符和s2前 j 个字符的LCS(最长公共子序列)
首先讨论D[i][j]的值
- 如果s1的第 i 个字符和s2的第 j 个字符相等那么D[ i ][ j ]等于s1前 i-1 个字符和s2前 j-1 个字符的LCS的值加 1 即D[ i ][ j ] = 1+D[ i-1 ][ j-1 ]
- 如果s1的第 i 个字符和s2的第 j 个字符不相等那么D[ i ][ j ]等于s1前 i-1 个字符和s2前 j 个字符的LCS的值和s1前 i 个字符和s2前 j-1 个字符的LCS的值中的最大值。
- 如果 i 或 j 等于0那么此时 D[ i ][ j ] = 0,因为 i 和 j 其中一个为0的话就是一个空序列,那么空序列与其他序列的最长公共子序列为0.
那么根据上述描述的关系就可以构建 D[ i ][ j ]
序列 s1 = "abcbdab"
和s2 = "bdcabc"
的长度分别为7和6那么我们构建的二维数组D应该是8*7的因为这样我们才能使D[ i ][ j ]的值是s1前 i 个字符和s2前 j 个字符的LCS(最长公共子序列)
- 首先在第0行和第0列肯定都是0,因为是我们上面讨论的第三条
- 然后看第1行第1列,s1中的字符与s2中的字符不相等所以取左边和上边的最大值,是我们上面讨论的第二条所以这里为0
- 然后同理第第1行第2列与第1行第3列都是0,在第1行第4列时,s1中的字符与s2中的字符相等所以此处的值为D[ i-1 ][ j-1 ]的值+1,是我们上面讨论的第一条
- 然后再看第1行第5列s1中的字符与s2中的字符不相等所以取左边和上边的最大值,所以此处值为1,其他的类似直到把这个二维矩阵填满。
回溯输出所有最长公共子序列
回溯时从最后二维数组的最后一个值开始,这个值就是我们的最长子序列的长度
- 首先看当前m行n列中s1[ m-1 ]与s2[ n-1 ]的值是否相等,如果相等则这个值就是我们最长子序列中的值
- 如果当前m行n列中s1[ m-1 ]与s2[ n-1 ]的值不相等,则比较左边和上方的值哪个和现在的值相等,相等则说明当前下标的值是从它过来的
- 当m行n列中s1[ m-1 ]与s2[ n-1 ]的值不相等且左边和上方的值相等时,需要在此处分叉,因为当前的值可以从它们两个中的任意一个过来
完整程序代码(python)
代码参考:链接: 【动态规划】输出两个序列的所有的最长公共子序列(java)
class Solution:
def __init__(self, text1: str, text2: str):
self.text1 = text1 #输入第一个序列
self.text2 = text2 #输入第二个序列
self.m, self.n = len(text1), len(text2) #m,n分别代表第一个序列和第二个序列的长度
self.arr = [[0 for j in range(self.n+1)] for i in range(self.m+1)] # 创建一个m*n的矩阵c并初始化元素为0
self.set1 = set() #创建一个集合用来存储所有的最长子序列
for i in range(1,self.m + 1): #用两个for循环来对二维矩阵arr填值
for j in range(1,self.n+ 1):
if self.text1[i-1] == self.text2[j-1]: #两个序列当前值相等的话取i-1和j-1的值+1,我们讨论的第一条
self.arr[i][j] = self.arr[i-1][j-1]+1
else: #不相等的话取左边和上边的最大值,我们讨论的第二条
self.arr[i][j] = max(self.arr[i][j-1],self.arr[i-1][j])
print(self.arr) #打印一下这个二维数组
def longestCommonSubsequence(self,i,j,s):#i是行,j是列,s是字符串
while i>0 and j>0: #回溯
if self.text1[i-1] == self.text2[j-1]: #回溯中的第一个
s += self.text1[i-1]
i -= 1
j -= 1
elif self.arr[i-1][j] > self.arr[i][j-1]: #回溯中的第二个
i -= 1
elif self.arr[i-1][j] < self.arr[i][j-1]: #回溯中的第二个
j -= 1
else: #最后一种情况就是回溯中讨论的第三个出现分叉
self.longestCommonSubsequence(i-1, j, s)
self.longestCommonSubsequence(i, j-1, s)
break; #分叉后让当前跳出循环,让两条分叉去分别递归
if len(s) == self.arr[-1][-1]: #如果当前回溯的字符串长度等于最长子序列的长度则把它加入到集合中
self.set1.add(s[::-1])
print("请输入第一个序列")
list1 = input() #读取第一个列表
print("请输入第二个序列")
list2 = input() #读取第二个列表
s1 = Solution(list1,list2) #创建一个对象
s1.longestCommonSubsequence(len(list1),len(list2),"") #调用最长子序列的函数
print(s1.set1) #输出集合中所有的最长子序列
#abcbdab
#bdcabc