最长公共子序列和最长子串问题

先看问题描述:有两个字符串s1,s2,求解他们的最长公共子序列(LCS)。举个例子:

s1="etc",s2="ecb",最长的公共子序列为"ec",长度为2

首先声明子序列、子串的区别。子序列不连续,字串连续。比如"ab","ac"都是字符串"abc"的子序列,而"ab"是"abc"子串,但"ac"不是,因为不连续。下面给出一个命题:

命题1:假如s1=[x_1,x_2,x_3...x_m],s2=[y_1,y_2...y_n],而s3=[z_1,z_2...z_k]是他们的一个最长公共子序列,则:

(1)若x_m=y_n,则s3=[z_1,z_2...z_{k-1}][x_1,x_2,x_3...x_{m-1}],[y_1,y_2...y_{n-1}]的一个最长公共子序列。证明:若不然,存在一个更长的公共子序列s4,那么s4也是s1=[x_1,x_2,x_3...x_m],s2=[y_1,y_2...y_n]的公共子序列,又因为x_m=y_n,那么他们的最长公共子序列长度一定大于s3=[z_1,z_2...z_k]的长度,矛盾。

(2)若x_m \neq y_n,那么z_k \neq y_n则有s3=[z_1,z_2...z_k]s1=[x_1,x_2,x_3...x_m],s4=[y_1,y_2...y_{n-1}]的最长公共子序列。证明:首先s3=[z_1,z_2...z_k]已经是s1,s4的公共子序列了,若存在更长的公共子序列s_5,那么s_5也是s1,s2的公共子序列且长度大于s_3,预定义矛盾。

(3)若x_m \neq y_n,那么z_k \neq x_m则有s3=[z_1,z_2...z_k]s4=[x_1,x_2,x_3...x_{m-1}],s2=[y_1,y_2...y_{n}]的最长公共子序列。证明:同上。

根据以上命题就可以给出状态数组和状态转移方程。定义dp[i][j],1<=i<=m,1<=j<=n

dp[i][j]表示[x_1,x_2,x_3...x_{i}],[y_1,y_2...y_{j}]的最长公共子序列长度,状态方程如下:

d[i][j]= \begin{cases} dp[i-1][j-1]+1, &if \ s1[i-1] = s2[j-1]\\ dp[i-1][j], &if\ dp[i][j-1]<dp[i-1][j]\\ dp[i][j-1],&if\ dp[i][j-1]>=dp[i-1][j] \end{cases}

下面贴出代码:

int LCS(string s1, string s2) {
    int m = s1.length();
    int n = s2.length();
    int ** dp = new int*[m+1];
    for(int i = 0;i < m+1;i++)
    {
        dp[i] = new int[n+1]();
    }
    for(int i = 1;i <= m;i++)
    {
        for(int j = 1;j <= n;j++)
        {
            if(s1[i-1] == s2[j-1])
            {
                dp[i][j] = dp[i-1][j-1] + 1;
            }
            else if(dp[i-1][j] < dp[i][j-1])
            {
                dp[i][j] = dp[i][j-1];
            }
            else{
                dp[i][j] = dp[i-1][j];
            }
        }   
    }
    return dp[m][n];
}

再看如下问题:有两个字符串s1,s2,求解ASCII之和最大的公共子序列。举个例子:

输入: s1 = "sea", s2 = "eat"
输出: 198
解释: ASCII之和最大公共子序列"ea",ASCII之和101 + 97 = 198。

我们仍然套用LCS的模板,先给出如下命题(证明过程同命题1):

命题1:假如s1=[x_1,x_2,x_3...x_m],s2=[y_1,y_2...y_n],而s3=[z_1,z_2...z_k]是他们的一个ACSCII之和最大的公共子序列,和为N,则:

(1)若x_m=y_n,则N-x_m[x_1,x_2,x_3...x_{m-1}],[y_1,y_2...y_{n-1}]的一个ACSCII之和最大公共子序列的和。

(2)若x_m \neq y_n,那么z_k \neq y_n则有Ns1=[x_1,x_2,x_3...x_m],s4=[y_1,y_2...y_{n-1}]的最长公共子序列。

(3)若x_m \neq y_n,那么z_k \neq x_m则有Ns4=[x_1,x_2,x_3...x_{m-1}],s2=[y_1,y_2...y_{n}]的最长公共子序列。

状态数组 dp[i][j],1<=i<=m,1<=j<=n, dp[i][j]表示[x_1,x_2,x_3...x_{i}],[y_1,y_2...y_{j}]的ACSCII之和最大公共子序列的和,状态方程如下表示,:

d[i][j]= \begin{cases} dp[i-1][j-1]+x_m, &if \ s1[i-1] = s2[j-1]\\ dp[i-1][j], &if\ dp[i][j-1]<dp[i-1][j]\\ dp[i][j-1],&if\ dp[i][j-1]>=dp[i-1][j] \end{cases}

代码如下:

int MCS(string s1, string s2) {
    int m = s1.length();
    int n = s2.length();
    int ** dp = new int*[m+1];
    for(int i = 0;i < m+1;i++)
    {
        dp[i] = new int[n+1]();
    }
    for(int i = 1;i <= m;i++)
    {
        for(int j = 1;j <= n;j++)
        {
            if(s1[i-1] == s2[j-1])
            {
                dp[i][j] = dp[i-1][j-1] + s1[i-1];
            }
            else if(dp[i-1][j] < dp[i][j-1])
            {
                dp[i][j] = dp[i][j-1];
            }
            else{
                dp[i][j] = dp[i-1][j];
            }
        }   
    }
    return dp[m][n];
}

接下来解决两个字符串最大公共子串问题,先看问题:

给两个字符串s1 和 s2 ,返回两公共的、长度最长的子串的长度。举个例子;

输入:
s1: "12321"
s2: "32147"
输出:3
解释:
长度最长的公共子串是 "321" 。

可以看到子串与子序列相比有连续性,不会发生断裂。根据以上性质,继续分析。假设Ns1=[x_1,x_2,x_3...x_{m-1}],s2=[y_1,y_2...y_{n-1}]的最长的、且都以x_{m-1},y_{n-1}结尾的最长子序列,就是说s3=[z_1,z_2...z_k]是最大公共子串,且满足x_{m-1}=z_k,y_{n-1}=z_k。如果x_{m}=y_{n},则s1=[x_1,x_2,x_3...x_{m}],s2=[y_1,y_2...y_{n}]满足以上条件最长子串长度为N+1,如果x_{m}\neq y_{n},那么最长子串长度为0。证明过程可用发证法。

根据以上分析得出状态数组 dp[i][j],1<=i<=m,1<=j<=n, dp[i][j]表示[x_1,x_2,x_3...x_{i}],[y_1,y_2...y_{j}]的最长且以x_i,y_j结尾的最长公共子串的长度,状态方程如下表示:

d[i][j]= \begin{cases} dp[i-1][j-1]+1, &if \ s1[i-1] = s2[j-1]\\ 0, &if\ s1[i-1] \neq s2[j-1]\end{cases}

遍历所有dp[i][j]就能找到答案,代码如下:

int findLength(string s1, string s2) {
    int m = s1.length();
    int n = s2.length();
    int ** dp = new int*[m+1];
    for(int i = 0;i < m+1;i++)
    {
        dp[i] = new int[n+1]();
    }
    int counter = 0;
    for(int i = 1;i <= m;i++)
    {
        for(int j = 1;j <= n;j++)
        {
            if(s1[i-1] == s2[j-1])
            {
                dp[i][j] = dp[i-1][j-1] + 1;
            }
            counter = max(counter,dp[i][j]);
        }
    }
    return counter;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值