最长公共子序列--DP

来源:互联网 发布:群晖bt端口 编辑:程序博客网 时间:2024/05/01 13:51
请编写一个函数,输入两个字符串,求它们的最长公共子串,并打印出最长公共子串。
例如:输入两个字符串BDCABA 和ABCBDAB,字符串BCBA 和BDAB 都是是它们的最长公共子
串,

则输出它们的长度4,并打印任意一个子串。

先介绍LCS 问题的性质:记Xm={x0, x1,…xm-1}和Yn={y0,y1,…,yn-1}为两个字符串,而
Zk={z0,z1,…zk-1}是它们的LCS,则:
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 是Yn-1 和X 的LCS;

反推关系:


有了上面的性质, 我们可以得出如下的思路: 求两字符串Xm={x0, x1,…xm-1} 和Yn={y0,y1,…,yn-1}的LCS,如果xm-1=yn-1,那么只需求得Xm-1 和Yn-1 的LCS,并在其后添加xm-1(yn-1)即可;如果xm-1≠yn-1,我们分别求得Xm-1 和Y 的LCS 和Yn-1 和X的LCS,并且这两个LCS 中较长的一个为X 和Y 的LCS


如果我们记字符串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 题系列第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)保存移动的方向。

参考代码如下:
#include "string.h"
// directions of LCS generation
enum decreaseDir {kInit = 0, kLeft, kUp, kLeftUp};
/////////////////////////////////////////////////////////////////////////////
// Get the length of two strings' LCSs, and print one of the LCSs
// Input: pStr1 - the first string
// pStr2 - the second string
// Output: the length of two strings' LCSs
/////////////////////////////////////////////////////////////////////////////
int LCS(char* pStr1, char* pStr2)
{
if(!pStr1 || !pStr2)
return 0;
size_t length1 = strlen(pStr1);
size_t length2 = strlen(pStr2);
if(!length1 || !length2)
return 0;
size_t i, j;

// initiate the length matrix
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;
// initiate the direction matrix
int **LCS_direction;
LCS_direction = (int**)(new int[length1]);
for( i = 0; i < length1; ++ i)
LCS_direction[i] = (int*)new int[length2];
for(i = 0; i < length1; ++ i)
for(j = 0; j < length2; ++ j)
LCS_direction[i][j] = kInit;
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_direction[i][j] = kLeftUp;
}
else
LCS_length[i][j] = 0;
}
// a char of LCS is found,
// it comes from the left up entry in the direction matrix
else if(pStr1[i] == pStr2[j])
{
LCS_length[i][j] = LCS_length[i - 1][j - 1] + 1;
LCS_direction[i][j] = kLeftUp;
}

// it comes from the up entry in the direction matrix
else if(LCS_length[i - 1][j] > LCS_length[i][j - 1])
{
LCS_length[i][j] = LCS_length[i - 1][j];
LCS_direction[i][j] = kUp;
}
// it comes from the left entry in the direction matrix
else
{
LCS_length[i][j] = LCS_length[i][j - 1];
LCS_direction[i][j] = kLeft;
}
}
}
LCS_Print(LCS_direction, pStr1, pStr2, length1 - 1, length2 - 1);
return LCS_length[length1 - 1][length2 - 1];
}
/////////////////////////////////////////////////////////////////////////////
// Print a LCS for two strings
// Input: LCS_direction - a 2d matrix which records the direction of
// LCS generation
// pStr1 - the first string
// pStr2 - the second string
// row - the row index in the matrix LCS_direction
// col - the column index in the matrix LCS_direction
/////////////////////////////////////////////////////////////////////////////
void LCS_Print(int **LCS_direction,
char* pStr1, char* pStr2,
size_t row, size_t col)
{
if(pStr1 == NULL || pStr2 == NULL)
return;
size_t length1 = strlen(pStr1);
size_t length2 = strlen(pStr2);
if(length1 == 0 || length2 == 0 || !(row < length1 && col < length2))
return;
// kLeftUp implies a char in the LCS is found

if(LCS_direction[row][col] == kLeftUp)
{
if(row > 0 && col > 0)
LCS_Print(LCS_direction, pStr1, pStr2, row - 1, col - 1);
// print the char
printf("%c", pStr1[row]);
}
else if(LCS_direction[row][col] == kLeft)
{
// move to the left entry in the direction matrix
if(col > 0)
LCS_Print(LCS_direction, pStr1, pStr2, row, col - 1);
}
else if(LCS_direction[row][col] == kUp)
{
// move to the up entry in the direction matrix
if(row > 0)
LCS_Print(LCS_direction, pStr1, pStr2, row - 1, col);
}
}
扩展:如果题目改成求两个字符串的最长公共子字符串,应该怎么求?子字符串的定义和子
串的定义类似,但要求是连续分布在其他字符串中。比如输入两个字符串BDCABA 和ABCBDAB
的最长公共字符串有BD 和AB,它们的长度都是2。
//July 注,更多可参考算法导论一书第15 章动态规划问题。
//及我针对此题写的一篇博文:24 个经典算法系列:3、动态规划算法解一道面试题
//http://blog.csdn.net/v_JULY_v/archive/2010/12/31/6110269.aspx

原创粉丝点击