最长公共子序列和最长重复子数组动规

1 最长公共子序列

 定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。
若这两个字符串没有公共子序列,则返回 0。

1.1思路1

 思路:动规的解法主要是定义子问题和写出子问题的递推关系,对于这道题,我们可以定义 d p [ i ] [ j ] dp[i][j] dp[i][j]是将原问题两个字符串 text1 和 text2的最长公共子序列的长度缩减为text1的前i个字符text1[0,…,i] 和 text2的j个字符text2[0,…,j] 的最长公共子序列的长度。
可写出状态转移方程如下:
t e x t 1 [ i ] = = t e x t 2 [ j ] text1[i]==text2[j] text1[i]==text2[j], d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 dp[i][j]=dp[i-1][j-1]+1 dp[i][j]=dp[i1][j1]+1
t e x t 1 [ i ] ! = t e x t 2 [ j ] text1[i]!=text2[j] text1[i]!=text2[j], d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) dp[i][j]=max(dp[i-1][j],dp[i][j-1]) dp[i][j]=max(dp[i1][j],dp[i][j1])
 其实很好理解,直观的 d p [ i ] [ j ] dp[i][j] dp[i][j]是可以从 d p [ i − 1 ] [ j − 1 ] , d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] dp[i-1][j-1],dp[i-1][j],dp[i][j-1] dp[i1][j1],dp[i1][j],dp[i][j1]这三个状态转移过来的,而 d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] dp[i-1][j],dp[i][j-1] dp[i1][j],dp[i][j1]是可以从 d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] dp[i1][j1]转移过来的,所以可以去掉, d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) dp[i][j]=max(dp[i-1][j],dp[i][j-1]) dp[i][j]=max(dp[i1][j],dp[i][j1])。而从另外一个方面来看,当 t e x t 1 [ i ] ! = t e x t 2 [ j ] text1[i]!=text2[j] text1[i]!=text2[j]时,因为我们不要求字符字符串时连续的, d p [ i ] [ j ] dp[i][j] dp[i][j]的值可以继承自 d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] dp[i-1][j],dp[i][j-1] dp[i1][j],dp[i][j1]。这点很重要,因为我们一会对定义的dp数组还要初始化。

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
         if(text1.size()==0||text2.size()==0)
         {
             return 0;
         }
         int l_a=text1.size();
         int l_b=text2.size();
         vector<vector<int>> dp(l_a,vector<int>(l_b,0));
         //初始化
          if(text1[0]==text2[0])
             {
                 dp[0][0]=1;
             }
         for(int i=1;i<l_a;i++)
         {
             dp[i][0]=dp[i-1][0];//当dp[i-1][0]==1时,即使text1[i]!=text2[0],dp[i][0]也要等于1
             if(text1[i]==text2[0])
             {
                 dp[i][0]=1;
             }
         }
         for(int j=1;j<l_b;j++)
         {
             dp[0][j]=dp[0][j-1];
             if(text1[0]==text2[j])
             {
                 dp[0][j]=1;
             }
         }
         //状态转移
         for(int i=1;i<l_a;i++)
         {
             for(int j=1;j<l_b;j++)
             {
                 if(text1[i]==text2[j])
                    dp[i][j]=dp[i-1][j-1]+1;
                else
                {
                    dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
                }
             }
         }
         return dp[l_a-1][l_b-1];
    }
};

1.2 思路2

 思路:我们在思路1选择的 d p [ i ] [ j ] dp[i][j] dp[i][j]的含义是当字符串text1 [ 0 , 1 , 2 , . . . i ] [0,1,2,...i] [0,1,2,...i]和text2 [ 0 , 1 , 2 , . . . j ] [0,1,2,...j] [0,1,2,...j]时的最长公共子序列,而我们注意到他需要初始化dp数组,这时因为dp[0][…]和dp[…][0]是真实代表字符串的元素。而在这里我们可以重新选择dp数组的含义, d p [ i ] [ j ] dp[i][j] dp[i][j]的含义是当字符串text1 [ 0 , 1 , 2 , . . . i ) [0,1,2,...i) [0,1,2,...i)和text2 [ 0 , 1 , 2 , . . . j ) [0,1,2,...j) [0,1,2,...j)时的最长公共子序列.,表示text1的前i个字符(不包括text1[i] )和text2的前j个字符(不包括text2[j] )的最长公共子序列。这时dp[0][…]和dp[…][0]是表示text1为空或text2为空时的最长公共子序列,默认值就是0。所以我们就不需要填充dp数组。

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
         if(text1.size()==0||text2.size()==0)
         {
             return 0;
         }
         int l_a=text1.size();
         int l_b=text2.size();
         vector<vector<int>> dp(l_a+1,vector<int>(l_b+1,0));
         for(int i=1;i<=l_a;i++)
         {
             for(int j=1;j<=l_b;j++)
             {
                 if(text1[i-1]==text2[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[l_a][l_b];
    }
};

其实这里还是可以将dp数组从二维空间的开销降低到一维的,代码和二维的差不多。

2 最长重复子数组

 给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。
输入:
A: [1,2,3,2,1]
B: [3,2,1,4,7]
输出: 3
解释:
长度最长的公共子数组是 [3, 2, 1]。

2.1思路1:

 最长重复子数组和最长公共子序列的思路类似,唯一的区别是子数组要求连续,而子序列不要求连续。这里 d p [ i ] [ j ] dp[i][j] dp[i][j]的定义为以 A [ i ] A[i] A[i]结尾和以 B [ j ] B[j] B[j]结尾的最长的子数组的长度。这里和最长公共子序列的定义是不同的,最长公共子序列的定义是 d p [ i ] [ j ] dp[i][j] dp[i][j]没有必要一定包括text1[i]和text2[j],只要是i或i之前,j或j之前的都可以转移过来。而最长重复子数组的 d p [ i ] [ j ] dp[i][j] dp[i][j]只能从 d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] dp[i1][j1]转移过来。
可写出状态转移方程如下:
t e x t 1 [ i ] = = t e x t 2 [ j ] text1[i]==text2[j] text1[i]==text2[j], d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 dp[i][j]=dp[i-1][j-1]+1 dp[i][j]=dp[i1][j1]+1
t e x t 1 [ i ] ! = t e x t 2 [ j ] text1[i]!=text2[j] text1[i]!=text2[j], d p [ i ] [ j ] = 0 dp[i][j]=0 dp[i][j]=0
 还有一点要注意的是是,因为我们定义的是以 A [ i ] A[i] A[i]结尾和以 B [ j ] B[j] B[j]结尾的最长的子数组的长度,我们最后还必须遍历整个dp数组找到最大值。这里我们直接定义一个动态更新的遍历ret来在计算dp数组的时候更新他。其实这个思路和我们之前讲的求数组的最大子序和的思路也差不多。

class Solution {
public:
//dp[i][j]=  (if(A[i]==B[i])) dp[i-1][j-1]+1  / else   0
//dp[i][j]表示以A[i]结尾和B[i]结尾的最长重复子数组
    int findLength(vector<int>& A, vector<int>& B) {
        if(A.size()==0||B.size()==0)
        return 0;
        int ret=INT_MIN;
        vector<vector<int>> dp(A.size(),vector<int>(B.size(),0));
        for(int i=0;i<B.size();i++)
        {
            if(A[0]==B[i])
           { 
               dp[0][i]=1;
               ret=1;
           }
        }
         for(int i=0;i<A.size();i++)
        {
            if(B[0]==A[i])
           {
                dp[i][0]=1;
               ret=1;
           }
        }
        for(int i=1;i<A.size();i++)
        {
             for(int j=1;j<B.size();j++)
             {
                if(A[i]==B[j])
                    dp[i][j]= dp[i-1][j-1]+1;
                else
                   dp[i][j]= 0;
                ret=max(ret,dp[i][j]);
             }
        }
        return ret;
    }
};

2.2思路2:

 同最长公共子序列,我们设dp[i][j]表示是当数组A [ 0 , 1 , 2 , . . . i ) [0,1,2,...i) [0,1,2,...i)和数组B [ 0 , 1 , 2 , . . . j ) [0,1,2,...j) [0,1,2,...j)时的最长公共子序列.即A[i-1]结尾和B[j-1]结尾的最长重复子数组.

class Solution {
public:
//dp[i][j]=  (if(A[i]==B[i])) dp[i-1][j-1]+1  / else   0
//dp[i][j]表示以A[i]结尾和B[i]结尾的最长重复子数组
    int findLength(vector<int>& A, vector<int>& B) {
        if(A.size()==0||B.size()==0)
        return 0;
        int ret=INT_MIN;
        vector<vector<int>> dp(A.size()+1,vector<int>(B.size()+1,0));
        for(int i=1;i<=A.size();i++)
        {
             for(int j=1;j<=B.size();j++)
             {
                if(A[i-1]==B[j-1])
                    dp[i][j]= dp[i-1][j-1]+1;
                else
                   dp[i][j]= 0;
                ret=max(ret,dp[i][j]);
             }
        }
        return ret;
    }
};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值