lcs 最長公共子序列 O(nlogn)算法

最長公共子序列問題:

給定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.     }  

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章