­

最长公共子序列你学会了吗

最长公共子序列

问题描述

给定两个字符串str1和str2,输出两个字符串的最长公共子序列。如果最长公共子序列为空,则返回”-1″。目前给出的数据,仅仅会存在一个最长的公共子序列。

示例:

输入:”abcde”,”ace”

输出:”ace”

分析问题

首先,我们先来看一下什么是子序列。一个字符串的子序列是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。例如,“ace”是“abcde”的子序列,但是“aec”不是“abcde”的子序列。这个问题和求最长公共子串类似,我们也可以采用动态规划的方法来求解。

我们假设str1[0,i-1]str2[0,j-1]的最长公共子序列为dp[i-1] [j-1],那么str1[0,i]str2[0,j]的最长公共子序列等于多少呢?这主要取决于字符str1[i]str2[j]是否相等,如果相等,则最长公共子序列会加1,即dp[i] [j] = dp[i-1] [j-1] + 1。如果不相等,则dp[i] [j] = max(dp[i-1] [j],dp[i] [j-1])。有了动态转移方程,我们只需要遍历字符串,逐步填写状态转移矩阵就好了。

下面我们来看一下代码如何实现。

def longestCommonSubsequence(str1,str2):
    n=len(str1)
    m=len(str2)
    dp=[[0 for _ in range(n)] for _ in range(m)]

    #处理边界条件
    if str1[0]==str2[0]:
        dp[0][0] = 1

    for i in range(n):
        if(str2[0]==str1[i]):
            dp[0][i]=1
        else:
            dp[0][i]=dp[0][i-1]

    for j in range(m):
        if(str1[0]==str2[j]):
            dp[j][0] = 1
        else:
            dp[j][0] = dp[j-1][0]


    for i in range(1, m):
        for j in range(1, n):
            if str2[i]==str1[j]:
                dp[i][j] = dp[i - 1][j - 1] + 1
            else:
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])

    return dp[m-1][n-1]

由于题目要求是输出具体的子序列,所以我们还需要根据状态转移矩阵逆推回去。

通过状态转移方程,我们可以知道dp[i] [j]要么从dp[i-1] [j-1] +1 求得,要么通过dp[i] [j-1] 和 dp[i-1] [j]的最大值得到,所以我们可以从dp[m-1] [n-1] 倒推出str1和str2的最大公共子序列。如下图所示。

我们来看一下完整的代码实现。

def LCS(self , str1 , str2 ):
        n=len(str1)
        m=len(str2)
        if n==0 or m==0:
           return -1
        dp=[[0 for _ in range(n)] for _ in range(m)]

        #处理边界条件
        if str1[0]==str2[0]:
            dp[0][0] = 1

        for i in range(n):
            if(str2[0]==str1[i]):
                dp[0][i]=1
            else:
                dp[0][i]=dp[0][i-1]

        for j in range(m):
            if(str1[0]==str2[j]):
                dp[j][0] = 1
            else:
                dp[j][0] = dp[j-1][0]


        for i in range(1, m):
            for j in range(1, n):
                if str2[i]==str1[j]:
                    dp[i][j] = dp[i - 1][j - 1] + 1
                else:
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])


        #通过状态转移矩阵倒推出子序列
        tmp=[]
        s1_index=n-1
        s2_index=m-1
        while s1_index !=0 and s2_index !=0:

            if str1[s1_index]==str2[s2_index]:
                tmp.append(str1[s1_index])
                s1_index=s1_index-1
                s2_index=s2_index-1
            else:
                if dp[s2_index-1][s1_index]>dp[s2_index][s1_index-1]:
                    s2_index = s2_index - 1
                else:
                    s1_index = s1_index - 1

        #单独处理第0行0列
        if s1_index==0:
            while s2_index>=0:
                if str1[0]==str2[s2_index]:
                    tmp.append(str1[0])
                    break
                s2_index=s2_index-1

        else:
            while s1_index>=0:
                if str1[s1_index]==str2[0]:
                    tmp.append(str1[s1_index])
                    break
                s1_index=s1_index-1
        if len(tmp)==0:
            return -1
        else:
            return "".join(tmp[::-1])

该算法的时间复杂度和空间复杂度都是O(n^2)。

更多有趣内容,请扫码关注一波~