題目:若是字符串一的全部字符按其在字符串中的順序出如今另一個字符串二中,ios
則字符串一稱之爲字符串二的子串。函數
注意,並不要求子串(字符串一)的字符必須連續出如今字符串二中。spa
請編寫一個函數,輸入兩個字符串,求它們的最長公共子串,並打印出最長公共子串。code
例如:輸入兩個字符串BDCABA和ABCBDAB,字符串BCBA和BDAB都是是它們的最長公共子串,遞歸
則輸出它們的長度4,並打印任意一個子串。ci
分析:求最長公共子串(Longest CommonSubsequence, LCS)是一道很是經典的動態規劃題。字符串
如下分析參見另外的一篇博文。源碼
步驟1、描述一個最長公共子序列string
先介紹LCS問題的性質:記Xm={x0, x1,…xm-1}和Yn={y0,y1,…,yn-1}爲兩個字符串,it
並設Zk={z0,z1,…zk-1}是X和Y的任意一個LCS,則可得出3條性質:
1. 若是xm-1=yn-1,那麼zk-1=xm-1=yn-1,而且Zk-1是Xm-1和Yn-1的一個LCS;
2. 若是xm-1≠yn-1,那麼當zk-1≠xm-1時,Z是Xm-1和Y的LCS;
3. 若是xm-1≠yn-1,那麼當zk-1≠yn-1時,Z是X和Yn-1的LCS;
下面簡單證實一下由上述相應條件得出的這些性質:
1. 若是zk-1≠xm-1,那麼咱們能夠把xm-1(yn-1)加到Z中獲得Z’,這樣就獲得X和Y的一個長度爲k+1的公共子串Z’。
這就與長度爲k的Z是X和Y的LCS相矛盾了。所以必定有zk-1=xm-1=yn-1。
既然zk-1=xm-1=yn-1,那若是咱們刪除zk-1(xm-一、yn-1)獲得的Zk-1,Xm-1和Yn-1,顯然Zk-1是Xm-1和Yn-1的一個公共子串,如今咱們證實Zk-1是Xm-1和Yn-1的LCS。用反證法不難證實。假設有Xm-1和Yn-1有一個長度超過k-1的公共子串W,那麼咱們把加到W中獲得W’,那W’就是X和Y的公共子串,而且長度超過k,這就和已知條件相矛盾了。
2. 仍是用反證法證實。假設Z不是Xm-1和Y的LCS,則存在一個長度超過k的W是Xm-1和Y的LCS,那W確定也X和Y的公共子串,而已知條件中X和Y的公共子串的最大長度爲k。矛盾。
3. 證實同2。
步驟2、一個遞歸解
根據上面的性質,咱們能夠得出以下的思路:
求兩字符串Xm={x0, x1,…xm-1}和Yn={y0,y1,…,yn-1}的LCS,
若是xm-1=yn-1,那麼只需求得Xm-1和Yn-1的LCS,並在其後添加xm-1(yn-1)便可(上述性質1);
若是xm-1≠yn-1,咱們分別求得Xm-1和Y的LCS和Yn-1和X的LCS,而且這兩個LCS中較長的一個爲X和Y的LCS(上述性質二、3)。
根據上述結論,可獲得如下公式,
若是咱們記字符串Xi和Yj的LCS的長度爲c[i,j],咱們能夠遞歸地求c[i,j]:
/ 0 if i<0 or j<0
c[i,j]= c[i-1,j-1]+1 if i,j>=0 and xi=xj
/ max(c[i,j-1],c[i-1,j] if i,j>=0 and xi≠xj
上面的公式用遞歸函數不難求得。天然想到Fibonacci第n項(本微軟等100題系列V0.1版第19題)問題的求解中可知,
直接遞歸會有不少重複計算,因此,咱們用從底向上循環求解的思路效率更高。
爲了可以採用循環求解的思路,咱們用一個矩陣(參考下文文末代碼中的LCS_length)保存下來當前已經計算好了的c[i,j],
當後面的計算須要這些數據時就能夠直接從矩陣讀取。
另外,求取c[i,j]能夠從c[i-1,j-1] 、c[i,j-1]或者c[i-1,j]三個方向計算獲得,
至關於在矩陣LCS_length中是從c[i-1,j-1],c[i,j-1]或者c[i-1,j]的某一個各自移動到c[i,j],
所以在矩陣中有三種不一樣的移動方向:向左、向上和向左上方,其中只有向左上方移動時才代表找到LCS中的一個字符。
因而咱們須要用另一個矩陣(參考下文文末代碼中的LCS_direction)保存移動的方向。
而後下面也是參見其博文後,修改部分所獲得的C++實現源碼:
// 動態規劃_最大子串.cpp : 定義控制檯應用程序的入口點。 // #include "stdafx.h" #include<string> #include<iostream> using namespace std; enum decreaseDir{kInit=0,kLeft,kUp,kLeftUp}; void LCS_Print(int** LCS_dirction,string pStr1,string pStr2,int row,int col); int LCS(string pStr1,string pStr2) { //if(!pStr1||!pStr2)return 0; int length1=pStr1.length(); int length2=pStr2.length(); if(!length1||!length2)return 0; int i,j; int** LCS_length; LCS_length=(int**)(new int[length1]); for(i=0;i<length1;i++) LCS_length[i]=(int*)new int[length2]; for(i=0;i<length1;++i) for(j=0;j<length2;++j) LCS_length[i][j]=0; //初始化length matrix int** LCS_dirction; LCS_dirction=(int**)(new int[length1]); for(i=0;i<length1;++i) LCS_dirction[i]=(int*)new int[length2]; for(i=0;i<length1;++i) for(j=0;j<length2;++j) LCS_dirction[i][j]=kInit; //初始化dirction matrix for(i=0;i<length1;++i) { for(j=0;j<length2;++j) { if(i==0||j==0) { if(pStr1[i]==pStr2[j]) { LCS_length[i][j]=1; LCS_dirction[i][j]=kLeftUp; } else LCS_length[i][j]=0; } else if(pStr1[i]==pStr2[j]) { LCS_length[i][j]=LCS_length[i-1][j-1]+1; LCS_dirction[i][j]=kLeftUp; } else if(LCS_length[i-1][j]>LCS_length[i][j-1]) { LCS_length[i][j]=LCS_length[i-1][j]; LCS_dirction[i][j]=kUp; } else { LCS_length[i][j]=LCS_length[i][j-1]; LCS_dirction[i][j]=kLeft; } } } LCS_Print(LCS_dirction,pStr1,pStr2,length1-1,length2-1); return LCS_length[length1-1][length2-1]; } void LCS_Print(int** LCS_dirction,string pStr1,string pStr2,int row,int col) { //if(pStr1==NULL||pStr2==NULL)return; int length1=pStr1.length(); int length2=pStr2.length(); if(length1==0||length2==0||!(row<length1&&col<length2))return; if(LCS_dirction[row][col]==kLeftUp) { if(row>0&&col>0) LCS_Print(LCS_dirction,pStr1,pStr2,row-1,col-1); printf("%c",pStr1[row]); } else if(LCS_dirction[row][col]==kLeft) { if(col>0) LCS_Print(LCS_dirction,pStr1,pStr2,row,col-1); } else if(LCS_dirction[row][col]==kUp) { if(row>0) LCS_Print(LCS_dirction,pStr1,pStr2,row-1,col); } } int _tmain(int argc, _TCHAR* argv[]) { string str1="BDCABA"; //char str1[]={'B','D','C','A','B','A'}; string str2="ABCBDAB"; //char str2[]={'A','B','C','B','D','A','B'}; cout<<"存在的一個最大子串爲:"<<endl; int Length=LCS(str1,str2); cout<<endl<<"最大子串的長度爲:"<<Length<<endl; int k=0; cin>>k; return 0; }
程序的運行截圖: