如果存在一個嚴格遞增而且長度大於0的下標序列{i1,i2……ik},使得對所有的j=1,2,……k,有x(ij)=z(j),那么我們稱Z是X的字符子序列。而且,如果Z既是X的字符子序列又是Y的字符子序列,那么我們稱Z為X和Y的公共字符序列。
在我們今天的問題中,我們希望計算兩個給定字符序列X和Y的最大長度的公共字符序列,這里我們只要求輸出這個最大長度公共子序列對應的長度值。
舉例來說,字符序列X=abcd,Y=acde,那么它們的最大長度為3,相應的公共字符序列為acd。
aedfhb

1 /* 2 遞歸思路: 3 當數組a和b對應位置字符相同時,則直接求解下一個位置; 4 否則,取兩種情況中的較大數值。 5 */ 6 #include<stdio.h> 7 #include<string.h> 8 char a[101],b[101];//定義字符串數組 9 int lena,lenb,lenz=0; 10 int maxlong(int i,int j){ 11 if(i>=lena || j>=lenb) return 0;//出口 12 if(a[i] == b[j]) return 1+maxlong(i+1,j+1);//直接求解下一個位置 13 else //取兩種情況中的較大數值 14 return maxlong(i+1,j) > maxlong(i,j+1) ? maxlong(i+1,j) : maxlong(i,j+1); 15 } 16 int main(){ 17 scanf("%s%s",&a,&b);//輸入字符串 18 lena=strlen(a);//獲取字符串長度 19 lenb=strlen(b); 20 printf("%d",maxlong(0,0));//輸出子串長度 21 return 0; 22 }
動態規划:時間復雜度大大降低。
1 #include<stdio.h> 2 #include<string.h> 3 char a[101],b[101]; 4 char num[102][102];//記錄中間結果的數組 5 //動態規划采用二維數組來標識中間計算結果,避免重復的計算來提高效率 6 void LCS(int lena,int lenb){ 7 for(int i=1;i<=lena;i++){ 8 for(int j=1;j<=lenb;j++){ 9 if(a[i-1]==b[j-1]){//注意這里的下標是i-1與j-1 10 num[i][j]=num[i-1][j-1]+1; 11 } 12 else{ 13 num[i][j]=num[i][j-1] > num[i-1][j] ? num[i][j-1] : num[i-1][j]; 14 } 15 } 16 } 17 } 18 int main(){ 19 scanf("%s%s",&a,&b);//輸入字符串 20 int lena = strlen(a);//獲取字符串長度 21 int lenb = strlen(b); 22 memset(num,0,sizeof(num));//數組賦初值 23 LCS(lena,lenb); 24 printf("%d",num[lena][lenb]); 25 return 0; 26 }
求兩字符串的最長子串,有兩種情況:一種是不要求在原串中連續(即上述情況),另一種要求子串在原字符串中是連續的。
下面轉載了網上的方法來做另一種情況的。
用矩陣來記錄中間結果:例如原串"bab"和"caba",則數組如下:
b a b
c 0 0 0
a 0 1 0
b 1 0 1
a 0 1 0
則矩陣的斜對角線最長的那個就是我們找的最長公共子串——求最長的由1組成的斜對角線:當要在矩陣是填1時讓它等於其左上角元素加1,如下:
b a b
c 0 0 0
a 0 1 0
b 1 0 2
a 0 2 0
這樣矩陣中的最大元素就是 最長公共子串的長度。
在構造這個二維矩陣的過程中由於得出矩陣的某一行后其上一行就沒用了,所以實際上在程序中可以用一維數組來代替這個矩陣(降低空間復雜度)。
以下代碼來自網絡:
1 #include<iostream> 2 #include<cstring> 3 #include<vector> 4 using namespace std; 5 //str1為橫向,str2這縱向 6 const string LCS(const string& str1,const string& str2){ 7 int xlen=str1.size(); //橫向長度 8 vector<int> tmp(xlen); //保存矩陣的上一行 9 vector<int> arr(tmp); //當前行 10 int ylen=str2.size(); //縱向長度 11 int maxele=0; //矩陣元素中的最大值 12 int pos=0; //矩陣元素最大值出現在第幾列 13 for(int i=0;i<ylen;i++){ 14 string s=str2.substr(i,1); 15 arr.assign(xlen,0); //數組清0 16 for(int j=0;j<xlen;j++){ 17 if(str1.compare(j,1,s)==0){ 18 if(j==0) 19 arr[j]=1; 20 else 21 arr[j]=tmp[j-1]+1; 22 if(arr[j]>maxele){ 23 maxele=arr[j]; 24 pos=j; 25 } 26 } 27 } 28 tmp.assign(arr.begin(),arr.end()); 29 } 30 string res=str1.substr(pos-maxele+1,maxele); 31 return res; 32 } 33 int main(){ 34 string str1("21232523311324"); 35 string str2("312123223445"); 36 string lcs=LCS(str1,str2); 37 cout<<lcs<<endl; 38 return 0; 39 }
最長公共子序列應用
有這樣的題目:
回文串是指正反都相同的字符串,比如abba,他正着看反着看都相同,給你一個字符串,長度小於1000,讓你刪除最少的字符使得該字符串成為回文串,例子:agddgca,刪除字符c就可以組成一個回文串。問最少刪除幾個字符?
想到可以借助最長公共子序列來解決,我們那上面那個例子看一下,我們把這個字符轉倒序:acgddga,可以看到他們的最長公共子序列剛好是最長回文串。其實倒過來的字符串和原來的字符串比較,就是那前面的跟后面的字符串比較,如果一樣,那么他們就是回文了。
所以求解這道題就比較簡單了:
1)先將字符串倒序排列
2)求兩個字符串飛串的最長公共子序列
3)用字符串的長度減去最長公共子序列就得到結果