题目:若是字符串一的全部字符按其在字符串中的顺序出如今另一个字符串二中,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; }
程序的运行截图: