LCS最长公共子序列问题 思路+个人题目理解+难点分析

问题       

给定两个字符串s1s2...sn​和t1t2...tm求出这两个字符串的最长公共子序列(LCS)的长度。

样例

n=4

m=4

s="abcd"

t="becd"

输出

3(#指"bcd")

题目理解

给定两个字符串,让你得出(两个字符串分别从头开始一个一个字符检查)有哪些字符是按顺序匹配的(每个字符只能匹配一次)。

比如对于样例,s中的第一个字符a在t中没有匹配的,跳过,到s中第二个字符b。t中第一个字符是b,匹配数为1,将b视为消去(不能再用于匹配)。然后s中第三个字符c和t中第二个字符e不匹配,和第三个字符c匹配,匹配数为2。s中第四个字符d又和t中第四个字符d匹配,匹配数为3.

最终结果为3。

解答

首先定义一个数组dp[MAX_N+1][MAX_M+1]

数组dp[i][j]存储s1s2...si和t1t2...tj字符串的最长公共子序列(LCS)。

递推公式为

dp[i+1][j+1]的结果可以有以下情况

1.当s[i+1]==t[j+1]时

dp[i+1][j+1]的值一定比dp[i][j]多1,多的那一个匹配可能是s[i+1]和t[j+1]配对,也可能不是,但是一定能保证多1,不多不少。

2.当s[i+1]!=t[j+1]时       

这时可以再细分为两种情况

one:

 看s1s2...si和t1t2...tjt(j+1)的匹配结果

two:

看s1s2...sis(i+1)和t1t2...tj的匹配结果

找大的那个

得到的递推关系为

dp[i+1][j+1]= 

dp[i][j]+1       ( if s(i+1)==t(j+1))       

max(dp[i][j+1],dp[i+1][j])      (else)

代码为

代码这里有个小坑,s和t的索引好像和dp的索引对不太上,后面解释

int n,m;
char s[MAX_N],t[MAX_M];
int dp[MAX_N+1][MAX_M+1];

void solve()
{
	
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<m;j++)
		{
			if(s[i]==s[j])
			{
				dp[i+1][j+1]=dp[i][j]+1;
			}
			else
			{
				dp[i+1][j+1]=max(dp[i+1][j],dp[i][j+1]);
			}
		}
	}
	printf("%d\n",dp[n][m]);
}

这个代码我当时理解上的最大问题就是dp的索引和s,j的索引对不上。

以下是我对这个现象的理解:

我认为是这个代码想要从头(第一个元素)执行到尾用的一个小技巧。毕竟递推式是dp[i+1][j+1],i和j都是从0开始的。遵循dp定义,应该从dp[1][1]开始。而s的索引是从0开始的。所以如果你仔细研究代码的话你会发现dp[i+1][j+1]对应的是s[i]和t[j]相比的时候,两者索引差1。

所以这时dp[i+1][j+1]的意义是s1s2...si和t1t2...tj字符串的最长公共子序列(LCS),这点和前面说的递推式不一样。

这样还有一个妙处,dp是全局变量,默认值是0,正好dp[1][1]用的dp[0][0]是默认值0,很妙。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值