题目描述 给定两个字符串str1和str2,输出两个字符串的最长公共子串,如果最长公共子串为空,输出-1。 示例1 输入 "1AB2345CD","12345EF" 返回值 "2345" niuke
给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。 一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。 若这两个字符串没有公共子序列,则返回 0。 示例 1: 输入:text1 = "abcde", text2 = "ace" 输出:3
解释:最长公共子序列是 "ace",它的长度为 3。 链接:
最长公共子串问题是典型的动态规划问题,是典型的动态规划问题:因为它要求的是最长公共子串,是在求子序列的问题,而动态规划就是为解决这样一类问题而生的,先复习一下动态规划的概念及基础知识:
动态规划算法
动态规划(Dynamic programming)是一种在数学、计算机科学和经济学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。 动态规划常常适用于有重叠子问题和最优子结构性质的问题,动态规划方法所耗时间往往远少于朴素解法。
动态规划背后的基本思想非常简单。大致上,若要解一个给定问题,我们需要解其不同部分(即子问题),再合并子问题的解以得出原问题的解。 通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量: 一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。 这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。 关于动态规划最经典的问题当属背包问题。背包问题详解
性质
- 最优子结构性质。如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。
- 子问题重叠性质。子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的效率。
- 无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。
步骤:
- 划分:按照问题的特征,把问题分为若干阶段。注意:划分后的阶段一定是有序的或者可排序的
- 确定状态和状态变量:将问题发展到各个阶段时所处的各种不同的客观情况表现出来。状态的选择要满足无后效性
- 确定决策并写出状态转移方程:状态转移就是根据上一阶段的决策和状态来导出本阶段的状态。根据相邻两个阶段状态之间的联系来确定决策方法和状态转移方程
- 边界条件:状态转移方程是一个递推式,因此需要找到递推终止的条件
即:
【初始状态】→【决策1】→【决策2】→…→【决策n】→【结束状态】
注意:
- 问题阶段
- 每个阶段的状态
- 相邻两个阶段之间的递归关系
解最长公共子序列
解题思路
- 定义动态规划数组dp[i][j] 即如果
str1=a1,a2,a3..
的长度为m,str2=b1,b2,b3...
的长度为n,则dp[i][j]表示为a1a2…ai和b1b2…bj的最长公共子序列的长度。因为i和j可能为0,即a_1,a_2,...,a_i和b1,b_2,..., b_j其中一个或同时为空的序列。 故此,有: - if i=0 or j=0: dp[i][j]=0;
- if i>0 and j>0:
- if a_i == b_i: dp[i][j]=dp[i-1][j-1]+1
- if a_i != b_i: dp[i][j]=max(dp[i][j-1],dp[i-1][j])
最长公共子串
最长公共子串:表示的是两个单词中连续的相同字母长度; 最长公共子序列:表示的是两个单词中相同的字母的长度。 他们的区别在于字符是否连续
几种最长公共子序列和最长公共子串的解法
ps :详细分析看代码:
from matplotlib import pyplot as plt
import numpy as np
"""
最长公共子串:表示的是两个单词中连续的相同字母长度
"""
class Solution:
#实现方法1
def LCS(self , str1 , str2 ):
# write code here
if len(str1) > len(str2):
str1, str2 = str2, str1
max_len, res = 0, ''
for i in range(len(str1)):
print("i:t",i)
print("i-max_len~i:",i-max_len,"~",i+1,"tstr1:t",str1[i-max_len:i+1])
if str1[i-max_len: i + 1] in str2:
res = str1[i-max_len: i + 1]
max_len += 1
print("res:t",res)
print("max_len:t",max_len)
if not res:
return '-1'
else:
return res
# 实现方法2
def getLcsChar(self,str1,str2):
if len(str1)>len(str2):
str1,str2= str2,str1
str1_len = len(str1)
str2_len = len(str2)
dp=[[0 for col in range(str2_len+1)] for row in range(str1_len+1) ]
p = 0 #最大匹配长度对应在str1中的最后
max_len = 0 #最大匹配长度
for row in range(str1_len):
for col in range(str2_len):
if str1[row]==str2[col]:
dp[row+1][col+1]=dp[row][col]+1
if dp[row+1][col+1]>max_len:
max_len=dp[row+1][col+1]
p = row+1
return str1[p-max_len:p], max_len
# 最长公共子序列的长度
def recursive_lcs(self,str_a, str_b):
if len(str_a)>len(str_b):
str_a,str_b=str_b,str_a
ret_c=""
if len(str_a) == 0 or len(str_b) == 0:
return 0
if str_a[0] == str_b[0]:
return self.recursive_lcs(str_a[1:], str_b[1:]) + 1
else:
return max([self.recursive_lcs(str_a[1:], str_b), self.recursive_lcs(str_a, str_b[1:])])
S=Solution()
str1="1AB2345CD"
str2="12345EF"
print("LCS:",S.LCS(str1,str2))
print("recursive_lcs",S.recursive_lcs(str1,str2))
str_ret, num=S.getLcsChar(str1,str2)
print("getLcsChar:",str_ret, num)
"""
最长公共子序列:表示的是两个单词中相同的字母的长度。
比如:fosh,与fish相近还是与fort相近呢?
如果用最长公共子串的方法则是相同的,都有两个字母连续相同。但是,
这里我们是不是应该一眼看出其实fish更相近一些呢,因为它有三个字母与fosh相同。
"""
class LCS:
"""
求str1,str2最长公共子序列
"""
def __init__(self,str1,str2):
self.str1=str1
self.str2=str2
self.lcs_ret=''
def getFlag(self):
"""
通过动态规划算法生成标记矩阵和结果矩阵
"""
# 获取字符串长度
str1_len=self.str1.__len__()
str2_len=self.str2.__len__()
# if str1_len>str2_len:
# self.str1, self.str2 = self.str2, self.str1
# str1_len=self.str1.__len__()
# str2_len=self.str2.__len__()
# 初始化结果矩阵和flag矩阵
ret_list=[[0 for col in range(str2_len+1)] for row in range(str1_len+1)]
flag_list=[["$" for col in range(str2_len+1)] for row in range(str1_len+1)]
# # 设flag_list中的第一行和第一列为str1,str2中的字符
for col in range(0,flag_list[0].__len__()-1):
#print(self.str2[col])
flag_list[0][col]=""+self.str2[col]
for row in range(1,flag_list.__len__()):
flag_list[row][0]=""+self.str1[row-1]
for row in range(str1_len):
for col in range(str2_len):
# 如果:a[row-1]=b[col-1],则a[:row]和b[:col]的最长子序列长度为a[:row-1]和b[:col-1]的最长子序列长度加一
if self.str1[row]==self.str2[col]:
ret_list[row+1][col+1]=ret_list[row][col]+1
flag_list[row+1][col+1]="ok"
# 否则:a[row-1]!=b[col-1],则a[:row]和b[:col]的最长子序列长度为{{a[:row-1],b[:col]},{a[:row],b[:col-1]|最长子序列长度的最大值}
else:
# ret_list[row+1][col+1]=max(ret_list[row+1][col],ret_list[row][col+1])
# if ret_list[row+1][col+1]==ret_list[row][col+1]:
# flag_list[row+1][col+1]="up"
# else:
# flag_list[row+1][row+1]="left"
if ret_list[row][col+1]>=ret_list[row+1][col]:
ret_list[row+1][col+1]=ret_list[row][col+1]
flag_list[row+1][col+1]="up"
elif ret_list[row][col+1]<ret_list[row+1][col]:
ret_list[row+1][col+1]=ret_list[row+1][col]
flag_list[row+1][col+1]="left"
return ret_list,flag_list, ret_list[-1][-1]
def getCls(self,flag,row,col):
"""
利用标记矩阵自底向上来求最优解
:param flag: 标记列表
:param row: 第一个子序列的长度
:param col: 第二个子序列的长度
:return:
"""
if row==0 or col==0:
return
if flag[row][col]=="ok":
self.getCls(flag,row-1,col-1)
self.lcs_ret=self.lcs_ret+self.str1[row-1]
#print(self.str1[row-1])
elif flag[row][col]=="left":
self.getCls(flag,row,col-1)
else:#flag[row][col]=="up"
self.getCls(flag,row-1,col)
def __str__(self):
"""
重载__str__方法,相当于Java中的对象toString()方法
:return:
"""
print("序列({})和序列({})的最长子序列为:({})".format(self.str1, self.str2, self.lcs_ret))
if __name__ == "__main__":
a = "xyxxzxyzxy"
b = "zxzyyzxxyxxz"
a = "BDCABA"
b = "ABCBDAB"
# a = "ace"
# b = "abcde"
my_lcs = LCS(a, b) # 构造一个具有两个字符串的对象
lcs_chart, lcs_flag,l = my_lcs.getFlag() # 获得计算列表和标记列表
# 显示结果矩阵和标记矩阵
print('结果矩阵如下:')
for i in lcs_chart:
print(i)
print("标记矩阵如下:")
for j in lcs_flag:
print(j)
# 求两个序列的最长子序列最优解并显示
my_lcs.getCls(lcs_flag, a.__len__(), b.__len__())
my_lcs.__str__()
print("最长公共子序列的长度:",l)
img_l=np.array(lcs_chart)
plt.figure("Image")
plt.imshow(lcs_chart)
plt.show
out
i: 0
i-max_len~i: 0 ~ 1 str1: 1
res: 1
max_len: 1
i: 1
i-max_len~i: 0 ~ 2 str1: 12
i: 2
i-max_len~i: 1 ~ 3 str1: 23
res: 23
max_len: 2
i: 3
i-max_len~i: 1 ~ 4 str1: 234
res: 234
max_len: 3
i: 4
i-max_len~i: 1 ~ 5 str1: 2345
res: 2345
max_len: 4
i: 5
i-max_len~i: 1 ~ 6 str1: 2345E
i: 6
i-max_len~i: 2 ~ 7 str1: 345EF
LCS: 2345
recursive_lcs 5
getLcsChar: 2345 4
结果矩阵如下:
[0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 1, 1, 1, 1, 1, 1]
[0, 0, 1, 1, 1, 2, 2, 2]
[0, 0, 1, 2, 2, 2, 2, 2]
[0, 1, 1, 2, 2, 2, 3, 3]
[0, 1, 2, 2, 3, 3, 3, 4]
[0, 1, 2, 2, 3, 3, 4, 4]
标记矩阵如下:
['A', 'B', 'C', 'B', 'D', 'A', 'B', '$']
['B', 'up', 'ok', 'left', 'ok', 'left', 'left', 'ok']
['D', 'up', 'up', 'up', 'up', 'ok', 'left', 'left']
['C', 'up', 'up', 'ok', 'left', 'up', 'up', 'up']
['A', 'ok', 'up', 'up', 'up', 'up', 'ok', 'left']
['B', 'up', 'ok', 'up', 'ok', 'left', 'up', 'ok']
['A', 'ok', 'up', 'up', 'up', 'up', 'ok', 'up']
序列(BDCABA)和序列(ABCBDAB)的最长子序列为:(BDAB)
最长公共子序列的长度: 4