最长公共子序列与最长公共子串区别

最长公共子序列与最长公共子串区别

假设s1=“abaaca”, s2=“acaaba”。

  • 最长公共子序列是s1和s2的不连续的最大公共子集。例如,s1和s2的最长公共子序列是[‘a’,’a’,’a’,’a’]
  • 最长公共子串是s1和s2中连续的最长公共子串。例如,s1和s2的最长公共子串是“aca”和“aba”

最长公共子序列c++实现

两个实现差不多,这里均使用动态规划实现。实现一是最普通的动态规划,实现二则进行了状态压缩。

实现一:无状态压缩

int lcs_normal(string s1, string s2)
{
    /*
    dp[i][j] 表示s1前i个字符和s2前j个字符的最长公共子序列
    base case: dp[0][j]=dp[i][0]=0
    状态转移方程:如果s1[i-1]==s2[j-1], dp[i][j] = dp[i-1][j-1]+1;
                 否则dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
    
    */

    int m = s1.size();
    int n = s2.size();
    vector<vector<int>> dp(m + 1, vector<int>(n + 1, 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;
            }
            else
            {
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    return dp[m][n];
}

实现二:状态压缩

int lcs_compress(string s1, string s2)
{
    /*
        dp[i][j] 依赖于 dp[i-1][j-1], dp[i-1][j],dp[i][j-1]
        一般状态压缩就是把行数这个信息给丢掉(因为最多依赖前一行i-1的值)
        我们按行数递增的方向填表,省去行信息后,dp[j] 就等价于 dp[i-1][j]
        然后由于依赖dp[i][j-1]和dp[i-1][j-1],这两个在没有行信息时都是dp[j-1]
        因此这个状态压缩无论对j倒序还是顺序都不能直接解决这个问题。这里我的实现是
        j进行顺序遍历,在dp[j-1]覆盖 dp[i-1][j-1]前,把dp[i-1][j-1]信息记录下来,假设为prev
        也就是说,在第二层循环内,在更新dp[j]前,使用prev记录它。
        需要注意的是,dp[j]本身也依赖于prev,在内循环j==1时,这个值哪来的?
        因此需要在第二层循环开始前,对这个值进行初始化。prev这个值初始化为dp[i-1][0],即dp[0]

    */
    int m = s1.size();
    int n = s2.size();
    vector<int> dp(n+1,0);
    for (int i = 1; i <= m; i++)
    {
        int prev = dp[0];
        for (int j = 1; j <= n; j++)
        {
            if (s1[i - 1] == s2[j - 1])
            {
                int tmp = dp[j];
                dp[j] = prev + 1;
                prev= tmp;
            }
            else
            {   int tmp = dp[j];
                dp[j] = max(dp[j], dp[j - 1]);
                prev= tmp;
            }
        }
    }
    return dp[n];
}

最长公共子串c++实现

实现一:无状态压缩

int lc_substring(string s1, string s2){
     /*
    dp[i][j] 表示以最后一个字符为s1[i-1]和s2[j-1]的最长公共子串
    base case: dp[0][j]=dp[i][0]=0
    状态转移方程:如果s1[i-1]==s2[j-1], dp[i][j] = dp[i-1][j-1]+1;
                 否则dp[i][j] = 0;
    最后并不是返回dp[m][n],而是max(dp[i][j])
    */

    int m = s1.size();
    int n = s2.size();
    int res=0;
    vector<vector<int>> dp(m + 1, vector<int>(n + 1, 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;
            }
            else
            {
                dp[i][j] = 0;
            }
            res = max(res, dp[i][j]);
        }
    }
    return res;
}

实现二:状态压缩

int lc_substring_compress(string s1, string s2){
     /*
        dp[i][j] 依赖于 dp[i-1][j-1],只需要前一行,因此可以直接省略行信息
        对j采用倒序遍历,则dp[j-1] 等价于dp[i-1][j-1].
    */

    int m = s1.size();
    int n = s2.size();
    int res=0;
    vector<int> dp(n+1,0);
    for (int i = 1; i <= m; i++)
    {
        for (int j = n; j >= 1; j--)
        {
            if (s1[i - 1] == s2[j - 1])
            {
                dp[j] = dp[j - 1] + 1;
            }
            else
            {
                dp[j] = 0;
            }
            res = max(res, dp[j]);
        }
    }
    return res;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值