公共子序列

原创作品,允许转载,转载时请务必以超链接形式标明文章  原始出处 、作者信息和本声明。否则将追究法律责任。 http://karsbin.blog.51cto.com/1156716/966387

最长公共子序列问题:

给定2个字符串,求其最长公共子串。如abcde和dbada的最长公共字串为bd。

动态规划:dp[i][j]表示A串前i个和B串前j个的最长公共子串的长度。

若A[i] == B[j] , dp[i][j] = dp[i-1][j-1] + 1;

否则 dp[i][j] = max(dp[i-1][j],dp[i][j-1]);

时间复杂度O(N*M)。

dp[i][j]仅在A[i]==B[j]处才增加,对于不相等的地方对最终值是没有影响的。

故枚举相等点处可以对其进行优化。

则对于dp[i][j](这里只计算A[i]==B[j]的i和j),取最大的dp[p][q],满足(p<i,q<j),通过二叉搜索树可以再logn的时间里获取到最大的dp[p][q],区间在[0,j)。

这里也可将其转化为最长递增子序列问题。

举例说明:

A:abdba

B:dbaaba

则1:先顺序扫描A串,取其在B串的所有位置:

    2:a(2,3,5) b(1,4) d(0)。

    3:用每个字母的反序列替换,则最终的最长严格递增子序列的长度即为解。

替换结果:532 41 0 41 532

最大长度为3.

简单说明:上面的序列和最长公共子串是等价的。

对于一个满足最长严格递增子序列的序列,该序列必对应一个匹配的子串。

反序是为了在递增子串中,每个字母对应的序列最多只有一个被选出。

反证法可知不存在更大的公共子串,因为如果存在,则求得的最长递增子序列不是最长的,矛盾。

最长递增子序列可在O(NLogN)的时间内算出。

dp[i] = max(dp[j]+1) ( 满足 a[i] > a[j] && i > j )

显然对于同样的如dp[k] = 3,假定k有多个,记为看k1,k2,.....,km 设k1 < k2 < .... < km

在计算dp[i]的时候,k2,k3,....,km显然对结果没有帮助,取当前最小的k,

满足ans[k] = p (最小的p使得dp[p]=k) ,每次二分,更新ans[dp[i]] = min(ans[dp[i]],i).

 

ps:LCS在最终的时间复杂度上不是严格的O(nlogn),不知均摊上是不是。

举个退化的例子:

如A:aaa

    B:aaaa

则序列321032103210

长度变成了n*m ,最终时间复杂度O(n*m*(lognm)) > O(n*m)。

这种情况不知有没有很好的解决办法。

附个参考代码:

  
  
  1. #include <stdio.h>  
  2. #include <ctype.h>  
  3. #include <string.h>  
  4. #include <iostream>  
  5. #include <string>  
  6. #include <math.h>  
  7. #include <vector>  
  8. #include <queue>  
  9. #include <algorithm>  
  10.  
  11. using namespace std;  
  12.  
  13. const int maxn = 1501 ;  
  14. vector<int> location[26] ;  
  15. int c[maxn*maxn] , d[maxn*maxn] ;  
  16.  
  17. inline int get_max(int a,int b) {   return a > b ? a : b ;  }  
  18.  
  19. //nlogn 求lcs  
  20. int lcs(char a[],char b[])  
  21. {  
  22.     int i , j , k , w , ans , l , r , mid ;  
  23.     for( i = 0 ; i < 26 ; i++) location[i].clear() ;  
  24.     for( i = strlen(b)-1 ; i >= 0 ; i--) location[b[i]-'a'].push_back(i) ;  
  25.     for( i = k = 0 ; a[i] ; i++)  
  26.     {  
  27.         for( j = 0 ; j < location[w=a[i]-'a'].size() ; j++,k++) c[k] = location[w][j] ;  
  28.     }  
  29.     d[1] = c[0] ;   d[0] = -1 ;  
  30.     for( i = ans = 1 ; i < k ; i++)  
  31.     {  
  32.         l = 0 ; r = ans ;  
  33.         while( l <= r )  
  34.         {  
  35.             mid = ( l + r ) >> 1 ;  
  36.             if( d[mid] >= c[i] ) r = mid - 1 ;  
  37.             else l = mid + 1 ;  
  38.         }  
  39.         if( r == ans ) ans++,d[r+1] = c[i] ;  
  40.         else if( d[r+1] > c[i] ) d[r+1] = c[i] ;  
  41.     }  
  42.     return ans ;  
  43. }  
  44.  
  45. int main()  
  46. {  
  47.     char a[maxn] , b[maxn] ;  
  48.     while (~scanf("%s%s",a,b))  
  49.     {  
  50.         printf("%d\n",lcs(a,b));  
  51.     }  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值