最长公共子序列

题目:如果字符串一的所有字符按其在字符串中的顺序出现在另外一个字符串二中,则字符串一称之为字符串二的子串。注意,并不要求子串(字符串一)的字符必须连续出现在字符串二中。请编写一个函数,输入两个字符串,求它们的最长公共子串,并打印出最长公共子串。 例如:输入两个字符串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]);
}


 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值