动态规划系列(6)——LCS(最长公共子序列问题)

6 篇文章 0 订阅
5 篇文章 0 订阅

公共子序列的解释,其他博客已经解释的很清楚了,求两个字符串的最长相同的字符组成形式,并且允许不连续,但是每个字符的顺序必须相同。比如 “我们爱吃苹果”和“我爱苹果”的最长子序列就是“我爱苹果”,可以看出结果顺序一致,且两个字符串中都存在这些字符。

一、动态规划的解决思路

假设两个字符串分别为str1、str2,从最后一位开始判断:

1、如果str1和str2的最后一位相等,则我们可以断定,公共字符串中肯定包含最后一位,所以此时最大公共字符串的长度应该是1加上str1和str2去掉最后一位后的最大公共字符串的长度;

2、如果str1和str2的最后一位不相等,则我们现在需要计算两个情况:第一,我们放弃str1的最后一位字符,求去掉最后一位字符的str1和完整的str2的最大公共子序列的长度;第二,我们放弃str2的最后一位字符,求去掉最后一位字符的str2和完整的str1的最大公共子序列的长度。求出上述两种情况的基础上,我们取其中的最大值。

f(i,j)=\left\{\begin{matrix} 0 & i=0||j=0\\ f(i-1,j-1)+1)&str1[i]=str2[j] \\ max(f(i-1,j),f(i,j-1))&str1[i]!=str2[j] \end{matrix}\right.

我们可以看出上面的思想依然是选择或不选择,传统的递归可以实现的情况。

(1)递归的解决代码

# ---------递归解决-------
'''
如果两个字符串的最后一位相等,则结果为两个字符串去掉最后一位后的LCS加最后一位;
如果两个字符串的最后一位不相等,则结果为每一个字符串单独去掉最后一位和另一个完整字符串的LCS中的大值。
'''

def lcs_rec(str1, str2):
    if len(str1) <= 0 or len(str2) <= 0:
        return 0
    elif str1[-1] == str2[-1]:
        return lcs_rec(str1[:-1], str2[:-1]) + 1
    else:
        sol_A = lcs_rec(str1[:-1], str2)
        sol_B = lcs_rec(str1, str2[:-1])
        if sol_A > sol_B:
            return sol_A
        return sol_B

(2)循环解决

递归的解决方案中,存在许多重复的计算,效率较低,所以我们采用“空间换时间”的策略,提高效率。

我们定义一个二维矩阵,矩阵大小为[len(str1)+1, len(str2)+1],每一维的大小为什么要加上1呢,因为要把字符串长度为0的情况考虑进去,这是循环展开计算的基础。假设矩阵为OPT,其中每一位OPT[i][j]的含义,代表字符串长度分别为i和j时的最大公共子序列长度。首先存在初始化的情况,当i或j分别为0时,此时OPT中对应位的值都是0。

如下表所示,假设str1长度为4,str2长度为5

i  /  j012345
0000000
10     
20     
30     
40     

表格中除i==0和j==0以外的每一个位置OPT[i][j],都可以采用上面的动态变化来求解。当前位置是否相等,相等的话,OPT[i][j]等于两个字符串分别去掉该位置字符的最长公共字符串长度加1(即OPT[i-1][j-1]+1);如果不相等,则计算OPT[i-1][j]和OPT[i][j-1]中的最大值。

循环的解决代码:

# ---------循环解法---------
'''
以空间换时间
'''
def lcs_dp(str1, str2):
    import numpy as np
    # opt 表示从前往后,字符串1前i位和字符串2前j位的最大公共子序列长度
    # 初始i == 0 or j == 0时,opt为0
    opt = np.zeros((len(str1)+1,len(str2)+1))
    for i in range(1, len(str1)+1):
        for j in range(1, len(str2)+1):
            # 此处opt的长度为(len(str1)+1,len(str2)+1),但是字符串要从0开始计算
            if str1[i-1] == str2[j-1]:
                opt[i][j] = opt[i-1][j-1] + 1
            else:
                sol_A = opt[i-1][j]
                sol_B = opt[i][j-1]
                opt[i][j] = max(sol_A, sol_B)
    return opt[len(str1)][len(str2)]

二、问题拓展

如果现在不是求最大公共子序列的长度,而是求最大公共连续子序列的长度。我们应该怎么考虑。动态转移方程

f(i,j)=\left\{\begin{matrix} 0 & i=0||j=0\\ f(i-1,j-1)+1)&str1[i]=str2[j] \\ 0&str1[i]!=str2[j] \end{matrix}\right.

OPT代表到达i,j时,能够一直连续的字符的最长数目;如果存在str1[i]不等于str2[j],则该位置的连续数目归0 ,然后再从后面开始查找连续子序列。相当于是求解局部最优,然后最后通过max求全局最优。

直接写循环的代码:

#------------如果把问题改成,求最长连续公共字符串---
def continue_lcs_dp(str1, str2):
    import numpy as np
    # opt 表示从前往后,字符串1前i位和字符串2前j位的最大公共子序列长度
    # 初始i == 0 or j == 0时,opt为0
    opt = np.zeros((len(str1)+1,len(str2)+1))
    for i in range(1, len(str1)+1):
        for j in range(1, len(str2)+1):
            # 此处opt的长度为(len(str1)+1,len(str2)+1),但是字符串要从0开始计算
            if str1[i-1] == str2[j-1]:
                opt[i][j] = opt[i-1][j-1] + 1
            else:
                opt[i][j] = 0
    return np.max(opt)

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值