题目:如果字符串一的所有字符按其在字符串中的顺序出现在另外一个字符串二中,则字符串一称之为字符串二的子串。注意,并不要求子串(字符串一)的字符必须连续出现在字符串二中。请编写一个函数,输入两个字符串,求它们的最长公共子串,并打印出最长公共子串。 例如:输入两个字符串BDCABA和ABCBDAB,字符串BCBA和BDAB都是是它们的最长公共子串,则输出它们的长度4,并打印任意一个子串。
思路:利用动态规划求解,首先找到状态转移方程。
符号约定,C1是S1的最右侧字符,C2是S2的最右侧字符,S1'是从S1中去除C1的部分,S2'是从S2中去除C2的部分。
LCS(S1, S2)等于下列3项的最大者:
(1) LCS(S1, S2')
(2) LCS(S1', S2)
(3) LCS(S1', S2') -- 如果C1不等于C2;LCS(S1', S2')+1 -- 如果C1等于C2
终止条件: 如果S1和S2都是为空串,则结果也是空串。
我们用一个矩阵(lcs_len)保存动态规划过程中子问题的解。这个矩阵中的每个数字代表了该行和该列之前的LCS的长度。与上面状态转移方程相对应,矩阵中每个格子里的数字应该这么填,它等于一下3项中的最大者:
(1) 上面一个格子里的数字
(2) 左边一个格子里的数字
(3) 左上角那个格子里的数字(如果C1不等于C2);左上角那个格子里的数字+1(如果C1等于C2)
为了能够获得其中一个最长子序列,我们利用另一个矩阵(lcs_dir)保存移动的方向,总共有三种方向(向上、向左、向左上方)对应上面的三项。其中只有向左上方移动时才表明找到LCS中的一个字符。
源码如下:
// LCS 的方向
enum DECR_DIR{
DEF = 0,
LEFT,
UP,
LEFTUP
};
// 输出其中一个最长公共子序列
void LCS_Print(int** lcs_dir, char* str1, char* str2, int row, int col)
{
if(str1 == NULL || str2 == NULL)
return;
int len1 = strlen(str1);
int len2 = strlen(str2);
if(len1==0 || len2==0 )
return;
// LEFTUP implies a char in the lcs is found
if(lcs_dir[row][col] == LEFTUP)
{
if(row>0 && col>0)
LCS_Print(lcs_dir, str1, str2, row-1, col-1);
cout<<str1[row-1];
}
else if(lcs_dir[row][col] == LEFT)
{
if(col>0)
LCS_Print(lcs_dir, str1, str2, row, col-1);
}
else if(lcs_dir[row][col] == UP)
{
if(row>0)
LCS_Print(lcs_dir, str1, str2, row-1, col);
}
}
// 计算最长公共子序列长度
// 输入:两个字符串;输出:字符串长度。
int LCS(char* str1, char* str2)
{
if(!str1 || !str2)
return 0;
int len1 = strlen(str1);
int len2 = strlen(str2);
if(!len1 || !len2)
return 0;
int i, j;
// length matrix and direction matrix
int** lcs_len;
int** lcs_dir;
lcs_len = (int**)(new int[len1+1]);
lcs_dir = (int**)(new int[len1+1]);
for (i = 0; i <= len1; ++i)
{
lcs_len[i] = (int*)new int[len2+1];
lcs_dir[i] = (int*)new int[len2+1];
}
for(i = 0; i < len1; ++i)
{
lcs_len[i][0] = 0;
lcs_dir[i][0] = DEF;
}
for (j = 0; j < len2; ++j)
{
lcs_len[0][j] = 0;
lcs_dir[0][j] = DEF;
}
for (i = 1; i <= len1; ++i)
{
for (j = 1; j <= len2; ++j)
{
// a char of LCS is found
if(str1[i-1] == str2[j-1])
{
lcs_len[i][j] = lcs_len[i-1][j-1]+1;
lcs_dir[i][j] = LEFTUP;
}
else
{
// it comes from the left
if(lcs_len[i][j-1] > lcs_len[i-1][j])
{
lcs_len[i][j] = lcs_len[i][j-1];
lcs_dir[i][j] = LEFT;
}
// it comes from the up
else
{
lcs_len[i][j] = lcs_len[i-1][j];
lcs_dir[i][j] = UP;
}
}
}
}
// Print a LCS
LCS_Print(lcs_dir, str1, str2, len1, len2);
cout<<endl;
return (lcs_len[len1][len2]);
}