LCS问题求解-动态规划

1.何为LCS问题:

在求解LCS问题之前,我们需要先了解一下什么叫做最长公共子序列 最长公共子序列:用我们最容易通俗理解的话语来解释的话,最长公共子序列就是两个或者多个串中,最长的相同的子序列 ps:子序列可以不连续,但是有先后的次序关系

LCS问题应用非常的广泛

2.如何求解LCS问题:

1.暴力搜索 LCS问题如果我们只仅限于两个的=字符串的话,我们的首先的思考的策略是暴力搜索,那么暴力搜索的复杂性到底有多大呢,我们稍微来计算一下,假设题目只让我们求两个序列的LCS,那么假设a串长是n,b串的长是m 那么很容易求得,a串的子序列的个数有2^n,b串的子序列的个数有2^m,那么我们对于每一种情况进行匹配的话,时间复杂度就会变成绝对无法容忍的O(2^n*2^m)指数的爆炸式增长,这是最恐怖的一点 那么如果我们要在k个串中查找LCS,那么这个指数式的时间是绝对无法容忍的

2.动态规划: 动态规划对于这类求解最优解的问题,往往都可以给出多项式时间的优化算法 那么问题来了,LCS的DP思路是什么呢:

定义状态:(这里还是假设求的是两个串的LCS) dp[i][j]:定义为a串第i位置b串第j位置以前的两个序列的最大的LCS,那么显而易见,dp[0][0]=0,dp[n][m]就是我们要求的最大值

状态转移方程: 1.a[i]=b[j]   dp[i][j]=dp[i-1][j-1]+1 2.a[i]!=b[j]   dp[i][j]=max(dp[i-1][j],dp[i][j-1])

对于上面的状态转移方程的解释: 当i,j位置的字符匹配的时候,我们i,j位置以前的LCS就是i-1,j-1位置以前的LCS的长度+1 当i,j位置的字符不匹配的时候,那么LCS的长度是不会增加的,但是我们有两种选择,就是i,j-1位置以前的和i,j-1位置以前的,我们选取最大的LCS作为当前的LCS即可

附图好理解:
《LCS问题求解-动态规划》

如上,其实使LCS长度增加的只是图上的额矩阵的对角线路径而已   核心代码示例:

for(int i=1;i<=n;i++)
{
	for(int j=1;j<=m;j++)
	{
		if(a[i]==b[j]) dp[i][j]=dp[i-1][j-1]+1;
		else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
	}
} 

print dp[n][m]

3.LCS的优化:

我们会发现动态规划虽然可以将我们求解的指数时间复杂度问题转化成O(n*n)的多项式时间复杂度,但是我们不能沪铝的是,DP的求解速录为了能够快速得到结果,开辟了大量的空间用来保存中间结果,是的我们的空间复杂度变的很臃肿O(n*m)的空间复杂度一旦我们限制内存的大小或者我们给出的额数据的范围过于巨大的话,我们就必须考虑对LCS的DP求解思路进行空间上的优化

优化策略: 我们发现,实际上,我们在计算下一个位置之前的时候,我们最多只用到了这么一些数据dp[i-1][j],dp[i-1][j-1],dp[i][j-1],也就是说,我们最多也就只用到了i-1行和之前的dp[i][j-]的计算结果,那么我们很明显的可以发现,我们完全可以通过
O(min(strlen(a),strlen(b)))的空间复杂度就可以完成整个DP的所有的操作,也就是说,我们把我们的状态压缩到一维数组

操作方案: 因为我们每次都会将新的结果覆盖到之前的一维数组之上,所以说,我们在进行计算当前的dp[i]的时候,必须提前将我们的dp[i-1](dp[i]的对角线的元素)记录下来(save),我们最后要计算的就是这么几个情况: 1.当前匹配:dp[i]=save+1; 2.当前不匹配:dp[i-1](刚计算完的)和dp[i]取最大值即可 所以说,我们这里的最核心的要点就是保存save变量

核心代码:

for(int i=1;i<=la;i++)
		{
			save=dp[0];
			for(int j=1;j<=lb;j++)
			{
				int k=dp[j];   //提取当前值 
				if(a[i]==b[j]) dp[j]=save+1;
				else dp[j]=max(dp[j-1],dp[j]);
				save=k;     //save记录 
			}
		}
		cout<<dp[lb]<<endl;  //注意这里要对应好 

4.例题解析:NYOJ36

LCS版题: 朴素LCS dp:

 
#include"iostream"
#include"cstdio"
#include"cstring"
#include"cstdlib"
#define N 1002

using namespace std;

int dp[N];
char a[N];
char b[N];
int la;
int lb;
int save;

int main()
{
	int t;
	cin>>t;
	while(t--)
	{
		memset(a,0,sizeof(a));
		memset(b,0,sizeof(b));
		memset(dp,0,sizeof(dp));
		scanf("%s",a+1);
		scanf("%s",b+1);
		la=strlen(a+1);
		lb=strlen(b+1);
		for(int i=1;i<=la;i++)
		{
			save=dp[0];
			for(int j=1;j<=lb;j++)
			{
				int k=dp[j];   //提取当前值 
				if(a[i]==b[j]) dp[j]=save+1;
				else dp[j]=max(dp[j-1],dp[j]);
				save=k;     //save记录 
			}
		}
		cout<<dp[lb]<<endl;  //注意这里要对应好 
	}
	return 0;
}        

优化后LCS:空间复杂度O(min(a,b))

 
#include"iostream"
#include"cstdio"
#include"cstring"
#include"cstdlib"
#define N 1002

using namespace std;

int dp[N];
char a[N];
char b[N];
int la;
int lb;
int save;

int main()
{
	int t;
	cin>>t;
	while(t--)
	{
		memset(a,0,sizeof(a));
		memset(b,0,sizeof(b));
		memset(dp,0,sizeof(dp));
		scanf("%s",a+1);
		scanf("%s",b+1);
		la=strlen(a+1);
		lb=strlen(b+1);
		for(int i=1;i<=la;i++)
		{
			save=dp[0];
			for(int j=1;j<=lb;j++)
			{
				int k=dp[j];   //提取当前值 
				if(a[i]==b[j]) dp[j]=save+1;
				else dp[j]=max(dp[j-1],dp[j]);
				save=k;     //save记录 
			}
		}
		cout<<dp[lb]<<endl;  //注意这里要对应好 
	}
	return 0;
}        
    原文作者:动态规划
    原文地址: https://blog.csdn.net/ltyqljhwcm/article/details/52544151
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞