python实现求字符串最长公共子串

本文介绍两种求解最长公共子串的方法:暴力法与动态规划,并对比了它们的时间与空间复杂度。暴力法时间复杂度为O^3,而动态规划优化前后的时间复杂度均为O^2,但空间复杂度从O^2优化到了O(n)。

本文主要参考http://www.cnblogs.com/ider/p/longest-common-substring-problem-optimization.html的讲解,本人自己用python实现了一下暴力法,动态规划。


# coding: utf-8

# ## 最长公共子串问题
# 
# ### 暴力法求解  VS  动态规划
# 
# ### 参考自http://www.cnblogs.com/ider/p/longest-common-substring-problem-optimization.html
# 

# ## 方法一:暴力法求解
# ### 时间复杂度(o^3),空间复杂度(1)

# In[2]:

get_ipython().magic('pdb on')


# In[1]:

import numpy as np


# In[18]:

# 获取字符串str1和str2最长公共子串的长度与起始位置
def getLongestSubstring(str1,str2):
    longest=0
    start_pos1=-1
    start_pos2=-1
    compares=0   # 记录比较次数

    for i in range(len(str1)):
        for j in range(len(str2)):
            length=0
            m=i
            n=j
            while str1[m]==str2[n]:
                compares+=1
                length+=1
                m+=1
                n+=1
                if (m>=len(str1))|(n>=len(str2)):
                    break
            if longest<length:
                compares+=1
                longest=length
                start_pos1=i
                start_pos2=j
    return longest,start_pos1,start_pos2,compares


# ## 方法二:动态规划
# ### 未优化时,时间复杂度(o^2),空间复杂度(o^2)

# In[17]:

# 通过动态规划的方法优化求解最长公共子串
def getLongestSubstring_dp(str1,str2):
    str1_len=len(str1)
    str2_len=len(str2)
    compares=0
    if (str1_len==0)|(str2_len)==0:
        return -1

    # 定义一个二维数据记录最长子串长度
    m=np.zeros([str1_len,str2_len],dtype=np.int)

    for j in range(str2_len):
        compares+=1
        m[0][j]=1 if str1[0]==str2[j] else 0

    for i in range(1,str1_len):
        compares+=1
        m[i][0]=1 if str1[i]==str2[0] else 0

        for j in range(1,str2_len):
            compares+=1
            m[i][j]=m[i-1][j-1]+1 if str1[i]==str2[j] else 0

    # 寻找矩阵中最大的数,也就是最长公共子串的长度
    longest=0
    start_pos1=-1
    start_pos2=-1
    for i in range(str1_len):
        for j in range(str2_len):
            if longest<m[i][j]:
                longest=m[i][j]
                start_pos1=i-longest+1
                start_pos2=j-longest+1

    return longest,start_pos1,start_pos2,compares


# ### 优化后,时间复杂度(o^2),空间复杂度(n)

# In[16]:

def getLongestSubstring_dp2(str1,str2):
    str1_len=len(str1)
    str2_len=len(str2)

    if (str1_len==0)|(str2_len==0):
        return -1

    start_pos1=0
    start_pos2=0
    longest=0
    compares=0

    m=np.zeros([2,str2_len],dtype=np.int)  # 只用两行就可以计算最长子串长度

    for j in range(str2_len):
        if str1[0]==str2[j]:
            compares+=1
            m[0][j]=1
            if longest==0:
                longest=1
                start_pos1=0
                start_pos2=j

    for i in range(1,str1_len):

        # 通过且运算计算出当前行和先前行
        cur=int((i&1)==1)
        pre=int((i&1)==0)

        if str1[i]==str2[0]:
            compares+=1
            m[cur][0]=1
            if longest==0:
                longest=1
                start_pos1=i
                start_pos2=0

        for j in range(1,str2_len):
            if str1[i]==str2[j]:
                compares+=1
                m[cur][j]=m[pre][j-1]+1
                if longest<m[cur][j]:
                    longest=m[cur][j]
                    start_pos1=i-longest+1
                    start_pos2=j-longest+1

    return longest,start_pos1,start_pos2,compares



# ## 测试

# In[19]:

str1='abcababdcababc'
str2='ababc'

length,s1,s2,com=getLongestSubstring(str1,str2)
print(length,s1,s2,com)

length,s1,s2,com=getLongestSubstring_dp(str1,str2)
print(length,s1,s2,com)

length_3,s1_3,s2_3,com_3=getLongestSubstring_dp2(str1,str2)
print(length,s1,s2,com)

### 关于环形字符串最长公共子串的算法实现 对于环形字符串中的最长公共子串问题,可以采用一种扩展的方法来处理。具体来说,在解决此类问题时,通常会先将原始字符串与其自身的副本连接起来形成一个新的字符串[^1]。 这样做是为了模拟环形特性,使得原本位于字符串两端的部分能够被视作连续部分进行比较。然而需要注意的是,在最终的结果中应当排除掉那些长度超过原字符串一半的情况,因为这些情况实际上代表了环绕整个圆周的情形而不是真正的子串[^2]。 为了找到两个给定字符串之间的最长公共子串,一般可利用动态规划方法。创建一个二维数组`dp`,其中`dp[i][j]`表示第一个字符串从位置i开始与第二个字符串从位置j开始的最大匹配长度。通过遍历这两个字符串并更新此表,可以在O(n*m)的时间复杂度内完成计算,这里n和m分别是两字符串的长度[^3]。 当应用于环形字符串场景时,则需对上述过程稍加调整: ```python def longest_common_substring(s1, s2): # 将s1复制一份拼接到后面以模拟循环效果 extended_s1 = s1 * 2 dp = [[0] * (len(s2)+1) for _ in range(len(extended_s1)+1)] max_length = 0 end_pos = 0 for i in range(1, len(extended_s1)+1): for j in range(1, len(s2)+1): if extended_s1[i-1] == s2[j-1]: dp[i][j] = dp[i-1][j-1] + 1 if dp[i][j] > max_length and i-dp[i][j]<len(s1): max_length = dp[i][j] end_pos = i else: dp[i][j] = 0 start_pos = end_pos - max_length result = "" while max_length>0: result += extended_s1[start_pos:start_pos+max_length] break return result[:min(max_length,len(s1))] ``` 该函数首先构建了一个延长版本的目标字符串用于捕捉可能跨越边界条件下的最大重复序列;接着运用标准的动态规划技术填充表格,并记录下最佳解的位置信息以便最后提取出实际的答案片段。值得注意的是,由于我们关心的是不超过原来字符串大小范围内的最优解,所以在每次发现新的候选者时都会额外检查其起始索引是否满足这一约束条件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值