关于公共子序列,由浅入深,可以有多种问题:
1.最长子序列的长度(或者同时返回最长公共子序列);
2.从序列A变换到序列B,最少需要多少步操作(或者说A与B的距离)。操作包括:1).删除一个字符 2).插入一个字符 3).修改一个字符为另一个字符
一、针对第一个问题,网络上有各种文章介绍了解决方法,这里只介绍最优的方法(动态规划法)
下面是西山居游戏程序类2013校园招聘的笔试原题:
/*函数原型 int GetCommonStr(const char * szStr1, const char * szStr2, char * szCommon, int nSize);
功能说明:找出两个字符串szStr1,szStr2中最大公共子字符串szCommon,szCommon的缓存大小为nSize。
要求:给出该函数的实现,并尽可能提高函数的健壮性*/
int GetCommonStr(const char * szStr1, const char * szStr2, char * szCommon, int nSize)
{
if(szStr1==NULL || szStr2==NULL || nSize<=0)
return 0;
int lcs = 0;
int *c = new int[strlen(szStr2)];
const char * index = szStr2;
for(int i=0; i<strlen(szStr1); i++)
{
for(int j=strlen(szStr2)-1; j>=0; j--)
{
if(*(szStr1+i)==*(szStr2+j))
{
if(i==0||j==0)
c[j]=1;
else
c[j]=c[j-1]+1;
if(lcs<c[j])
{
lcs = c[j];
index = szStr2+j+1-lcs;//定位最长序列的起点
}
}
else c[j]=0;
}
}
int maxlen=( lcs<nSize? lcs:nSize);
for(int i=0; i<maxlen; i++)
*(szCommon+i)=*(index+i);
*(szCommon+maxlen)='\0';
return lcs;
}
使用动态规划方法,时间复杂度是O(n1+n2);空间复杂度是O(n2)。其中n1、n2分别是字符串szStr1、szStr2的长度
二、第二个问题需要再第一个问题的基础上进行扩展。
这里就需要一个n1×n2的矩阵R,保存字符串szStr1、szStr2的动态关系。通过关系矩阵R,递归的求出两个字符串中所有的相对最长的公共子序列的长度之和。
比如说,第一次求出最长公共子序列,和子序列的长度,那么这个最长公共子序列把两个字符串szStr1、szStr2分别分割成了三段:头部、尾部和公共子序列。
然后递归的对两个字符串头部,尾部分别求最长公共子序列,直到没有公共子序列或者字符串长度为0为止。
下面是递归函数部分:
int MostCommonStrs(int **R, int row1, int row2, int col1, int col2)
{
if(row1>row2||col1>col2)
return 0;
int max=0;
int row, col;
for(int i=row2; i>=row1; i--)
{
for(int j=col2; j>=col1; j--)
if(max<R[i][j])
{
max=R[i][j];
row=i;col=j;
}
}
if(max==0)
return 0;
return max + MostCommonStrs( a, row1, row-max, col1, col-max) + MostCommonStrs( a, row+1, row2, col+1, col2);
}
下面代码是在第一个问题过程中,保存关系矩阵R,然后再递归的求头部和尾部的最长公共子序列:
int MostCommonStrs(const char * szStr1, const char * szStr2, char * szCommon, int nSize)
{
if(szStr1==NULL || szStr1==NULL || nSize<=0)
return 0;
int size1 = strlen(szStr1);
int size2 = strlen(szStr2);
if(size1<=0 || size2<=0)
return 0;
int lcs=0;
int index1, index2;
int *c = new int[size2];
int **R = new int*[size1];// size1×size2的矩阵
for(int i=0; i<size1; i++)
R[i] = new int[size2];
for(int i=0; i<size1; i++)
{
for(int j=size2-1; j>=0; j--)
{
if(szStr1[i]==szStr2[j])
{
if(i==0||j==0)
c[j]=1;
else
c[j]=c[j-1]+1;
if(lcs<c[j])
{
lcs=c[j];
index1=i-lcs+1;//最长公共子序列在szStr1开始的位置
index2=j-lcs+1;//最长公共子序列在szStr2开始的位置
}
}
else
c[j]=0;
R[i][j]=c[j];//保存公共序列关系矩阵
}
}
int maxlen=( lcs<nSize? lcs:nSize);
for(int i=0; i<maxlen; i++)
szCommon[i]=szStr2[index2+i];
szCommon[maxlen]='\0';
delete []c;
int mostCommon = lcs + MostCommonStrs( R, 0, index1-1, 0, index2-1)
+ MostCommonStrs( R, index1+lcs+1, size1-1, index2+lcs+1, size2-1);//对头部和尾部求最长公共子序列
for(int i=0; i<size1; i++)
delete []R[i];
delete R;
return mostCommon;
}
求得最多公共子串长度mostCommon之后,用两个字符串szStr1、szStr2中较长的长度max(size1, size2)减去mostCommon,结果就是俩个字符串szStr1、szStr2的距离。