算法学习笔记——动态规划:最长公共子串LCS、最长公共子序列LCS

最长公共子串LCS(Longest Common Substring)

给出两个字符串str1和str2,求他们的最长公共子串

  • 利用dp[i][j]表示某公共子串的最大长度,这个公共子串在str1中以str1[i]结尾,在str2中以str2[j]结尾
  • 依次比较str1与str2中的每一个字符str1[i]和str2[j]
  • str1[i]==str2[j],那么dp[i][j] = dp[i-1][j-1] + 1,这意味着找到了长度更长的公共子串
    (若两个字符串中,各自的前一个字符也相同,则dp[i-1][j-1]的值大于0,否则dp[i-1][j-1]为0)
  • str1[i]!=str2[j],那么dp[i][j] = 0

在这里插入图片描述

  • 遍历过程中,用maxLen不断记录并更新最长公共子串的长度,若需要输出子串内容,还需要另外用start1start2记录子串在str1、str2中的起始坐标
  • 实现时,为了防止dp访问越界,在dp表格的最左边和最上边各添加一行全0行,这样str1[i]、str2[j]改为对应dp[i+1]、[j+1](访问dp时,下标加1)
def longestSubStr(str1,str2):
    """查找str1、str2的最长公共子串,
        返回最长公共子串、该子串在str1的起始下标、在str2的起始下标
        若没有公共子串,返回'', 0, 0"""
    len1 = len(str1)
    len2 = len(str2)
    maxLen,start1,start2 = 0,0,0#用于更新当前的最长公共子串
    dp = [[0 for _ in range(len2+1)]for _ in range(len1+1)]
    for i in range(len1):
        for j in range(len2):
            #iadd1,jadd1=i+1,j+1
            if str1[i] == str2[j]:
                dp[i+1][j+1] = dp[i][j]+1
##            else:
##                dp[i+1][j+1] = 0
            if (maxLen < dp[i+1][j+1]):#更新当前的最长公共子串
                maxLen = dp[i+1][j+1]
                start1 = i-maxLen+1
                start2 = j-maxLen+1
    return str1[start1:start1+maxLen],start1,start2
================= RESTART: C:\Users\13272\Desktop\最长公共子串LCS.py =================
>>> longestSubStr('fish','hisa')
('is', 1, 1)
>>> longestSubStr('a','bcd')
('', 0, 0)

最长公共子序列LCS

给出两个序列x,y,找出他们的最长公共子序列LCS(Longest Common Subsequence)
与最长公共子串的区别在于,子串必须是多个相连的字符,而子序列不一定

LeetCode 1143. 最长公共子序列(LongestCommonSubsequence)
给出两个序列x,y,找出他们的最长公共子序列LCS的长度
如s1=‘abcde’,s2=‘aceb’,返回3

思路:

  1. 状态:s1的一个子串与s2的一个子串的LCS
    选择:对于s1/s2一个新字符,可以选择将其纳入考虑,看是否有更长的LCS
  2. dp[i][j]表示子串x[0:i]和子串y[0:j]的最长公共子序列长度

套路:两个字符串的动态规划,一般都需要二维dp数组,i、j分别与两个字符串挂钩

  • 如果是“连续”的子串问题,i与以s1[i]结尾的子串挂钩
  • 若果是“不连续”的子序列问题,i与s1[0…i]的子序列挂钩
  1. 找状态转移方程
    数学归纳思想,假定已知dp[0]...dp[i-1],怎么求dp[i]:对于字符s[i],可以将其拼接到其他递增序列上,求其中的最大值
    转移方程
  • x[i]==y[j],那么dp[i][j] = dp[i-1][j-1] + 1,这意味*找到了长度更长的公共子序列
  • str1[i]!=str2[j],那么dp[i][j] = max(dp[i-1][j],dp[i][j-1])
    分别比较没有这两个字符后的LCS(子串x[0:i-1]y[0:j]的LCS or 子串x[0:i]y[0:j-1]的LCS,选择较大的那一个)

实现:

  • 技巧:为防止状态转移方程中出现越界,在dp表格的最左边和最上边各添加一行全0行,这样dp[i]、[j]改为对应x[i-1]、y[j-1](访问dp时,下标加1)
  • maxLen、flag用于不断更新最长公共子序列和输出这个序列
  • 若需要打印LCS内容,额外使用flag数组记录当前的dp[i][j]是从左上/左/上来的,反推回去然后打印
    在这里插入图片描述
def printLcs(flag,a,i,j):
    global ans
    if i==0 or j==0:
        return
    if flag[i][j]=='OK':
        printLcs(flag,a,i-1,j-1)
        ans.append(a[i-1])
    elif flag[i][j]=='Left':
        printLcs(flag,a,i,j-1)
    else:
        printLcs(flag,a,i-1,j)

def longSubSeq(str1,str2):
    len1 = len(str1)
    len2 = len(str2)
    maxLen = 0
    c = [[0 for i in range(len2+1)]for i in range(len1+1)]
    flag = [[0 for i in range(len2+1)]for i in range(len1+1)]
    for i in range(len1+1):
        for j in range(len2+1):
            if i == 0 or j == 0:
                c[i][j] = 0
            elif str1[i-1] == str2[j-1]:
                c[i][j] = c[i-1][j-1]+1
                flag[i][j] = 'OK'# 从左上来的
                maxLen = max(maxLen,c[i][j])
            elif c[i][j-1] > c[i-1][j]:
                c[i][j] =c[i][j-1]
                flag[i][j] = 'Left'# 从左边来的
            else:
                c[i][j] =c[i-1][j]
                flag[i][j] = 'UP'# 从上面来的
                
    #需要获得最长公共子序列的内容时,创建全局变量ans并调用此函数
    printLcs(flag,str1,len1,len2)
    
    return maxLen
a='ABCBDAB'
b='BDCABA'
ans = []
maxLength = longSubSeq(a,b)
================= RESTART: C:\Users\13272\Desktop\最长公共序列LCS.py =================
>>> ans
['B', 'C', 'B', 'A']
>>> maxLength
4
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值