【python】求最长连续公共子序列长度的几种解法

题目:

 

给定两个序列X和Y,返回最长连续的公共子序列长度。如果没有连续公共子序列,返回0.

X和Y的元素都是整数。

示例:

输入:

1 5 7 3 4 5
7 3 4 4 5 7 -2

输出:

3

 说明:

最长的连续公共子序列是[7,3,4] (X[2:4] 和Y[0:2])

这道题在【leetcode1143】的基础上增加了公共子序列连续的限制。解法可以有以下几种:

题解:

1. 动态规划

创建m+1 行 n+1 列的二维数组dp,其中 dp[i][j] 表示 a和b分别以a[i-1],b[j-1]结尾的最长公共子序列的长度。

可以得到状态转移方程如下:

dp[i][j]=\left\{ \begin{aligned} dp[i-1][j-1]+1, \quad a[i-1]=b[j-1] \\ 0,\quad a[i-1]!=b[j-1] \\ \end{aligned} \right .

最终计算dp中的最大值即为最长公共连续子序列的长度。

def findLength(a,b):
    m,n=len(a),len(b)
    maxlength=0
    dp=[[0]*(n+1) for i in range(m+1)]
    for i in range(m+1):
        for j in range(n+1):
            if a[i-1]==b[j-1]:
                dp[i][j]=dp[i-1][j-1]+1
                maxlength=max(maxlength,dp[i][j])
    return maxlength

时间复杂度:O(mn)

空间复杂度:O(mn),可以优化至O(m)或O(n)(只存储dp矩阵的两列进行计算)

2. 后缀数组

def longestCommonSubstring(A, B):
    if A == None or B == None:
        return
    C = [9999]
    s = A + C + B
    indexOfSharp = len(A)
    SA = suffixArray(s)
    maxLen, maxIndex = 0, 0
    hasCommon = False
    for i in range(len(s) - 1):
        # 判断后缀数组中两个相邻元素对应的后缀字符串是否位于“#”两侧,即是否属于A和B两个不同字符串
        diff = (SA[i] - indexOfSharp) * (SA[i + 1] - indexOfSharp)
        if diff < 0:
            tempLen = comlen(s, SA[i], SA[i + 1])
            if tempLen > maxLen:
                maxLen = tempLen
                maxIndex = SA[i]
                hasCommon = True
    return (maxLen, s[maxIndex:maxIndex + maxLen]) if hasCommon else False
# 得到一个字符串的后缀数组
def suffixArray(s):
    if s == None or len(s) == 0:
        return None
    allSuffix = []
    for i in range(len(s)):
        allSuffix.append([s[i:], i])
    sortedList = sorted(allSuffix)
    SA = [w[1] for w in sortedList]
    return SA
# 比较得到后缀数组中两个相邻的元素分别对应的后缀字符串的最长前缀
def comlen(s, p, q):
    j = 0
    while j < len(s[p:]) and j < len(s[q:]) and s[p:p + j + 1] == s[q:q + j + 1]:
        j += 1
    return j

时间复杂度:O(nlgn)

空间复杂度:O(n^2)

3. 哈希表+二分查找

设最长公共子串的长度为x,那么x在0到min(len(X),len(Y))之间,并且满足二分的性质。因为如果存在长度为m的公共子序列,那么必然存在长度小于m的公共子序列。先哈希一下这两个序列,二分长度。每次check的时候,假设判断是否存在长度为k的公共子串,那么看字符串1和字符串2长度为k的子串的哈希值有没有相同的,如果有返回true,没有返回false;然后使用二分查找最长公共子串长度x.

def findLen(X, Y):
    base, mod = 113, 10**9 + 9

    def checkHash(l):

        # Construct hash value set of X[0:l]
        hx = 0
        for i in range(l):
            hx = (hx * base + X[i]) % mod
        hvx = {hx}
        pow_base = pow(base, l - 1, mod)
        for i in range(l, len(X)):
            hx = ((hx - X[i - l] * pow_base) * base + X[i]) % mod
            hvx.add(hx)
        
        # Construct hash value set of Y[0:l] and check whether there is an hash value that matches 
        hy = 0
        for i in range(l):
            hy = (hy * base + Y[i]) % mod
        if hy in hvx:
            return True
        for i in range(l, len(Y)):
            hy = ((hy - Y[i - l] * pow_base) * base + Y[i]) % mod
            if hy in hvx:
                return True

        return False

    # Binary Search
    l, r = 0, min(len(X), len(Y))
    res = 0
    while l <= r:
        m = (l + r) // 2
        if checkHash(m):
            res = m
            l = m + 1
        else:
            r = m - 1
    return res

时间复杂度:O(nlogn)

空间复杂度:O(n)

4.双指针

def findLength(a, b):
    aa = ''.join(a)
    l, s = 1, 0
    l1, l2 = len(a), len(b)
    rtn = 0
    while l <= l1 and l+s <= l2:
        e = s + l
        temp = ''.join(b[s:e])
        if temp in aa:
            rtn = max(rtn, l)
            l += 1
        else:
           if str(b[e-1]) not in aa:
                s = e
           else:
                s += 1
                
           if s + rtn >= l2:
                return rtn
    return rtn
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

超高校级退堂鼓表演艺术家

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值