LCS最长公共子串:线性dp

LCS最长公共子串

题目:

有两个字符串,串T为ABCBDAB,而串S为BDCABA,求最长公共子串的长度:
首先我们分析,如果有两个字符Ti和Sj,就有以下公式:
dp[i][j]=dp[i-1][j-1]+1; T[i]==S[j];
dp[i][j]=max(dp[i-1][j],dp[i][j-1]); T[i]!=S[j];
可以带本题例子证出,这里就不说明了。

代码:
#include<cstdio>
#include<cstring>
#include<algorithm> 
using namespace std;
const int MAXN=1e3;
char a[MAXN],b[MAXN];
int dp[MAXN][MAXN];
void LCS(int n,int m)
{
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			dp[i][j]=a[i]==b[j]?dp[i-1][j-1]+1:max(dp[i-1][j],dp[i][j-1]);
}
int main()
{
	scanf("%s%s",a+1,b+1);
	int len1=strlen(a+1);
	int len2=strlen(b+1);
	LCS(len1,len2);
	printf("%d\n",dp[len1][len2]);
	return 0;
} 

现在我们来分析一下复杂度,时间复杂度为O(n²),而空间复杂度为n*m;当然还有一种降低空间复杂度的方法也就是滚动数组,还不会这种方法就不在这里说明了,感兴趣的读者可以去搜一下,下面我们来讲一讲LCS的应用。

编译距离

一个原字符串T,变为一个目标串S,可以进行以下三种操作:
(1):将T(i)变为另一个字符
(2):将T(i)删除
(3):增加一个字符
每进行一次操作编译距离加一。问最小的编译距离为多少?
例如:将"ALGORITHM" 变为"ALTRUISTIC"
A L G O R I T H M
A L T R U I S T I C
我们可以看到这个跟LCS很类似,我们这里采用线性dp来解决。首先分析,T串和S串的最后一个字符,只有三种情况(空字符,字符),(字符,字符),(字符,空字符),所以:
第一种情况:dp[i][j]=dp[i][j-1]+1;
第二种情况:dp[i][j]=T[i]==T[j]?dp[i-1][j-1]:dp[i-1][j-1];
第三种情况:dp[i][j]=dp[i-1][j]+1;

代码
#include<cstdio>//编辑距离 
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=1e3+5;
char a[MAXN],b[MAXN];
int dp[MAXN][MAXN];
int LCS()
{
	int lena=strlen(a);
	int lenb=strlen(b);
	for(int i=1;i<=lena;i++)	dp[i][0]=i;//第一行,也就是一个字符串和空串肯定就是本身的操作次数 
	for(int i=1;i<=lenb;i++)	dp[0][i]=i;//第一列同理 
	for(int i=1;i<=lena;i++)
	{
		for(int j=1;j<=lenb;j++)
		{
			if(a[i-1]==b[j-1])	dp[i][j]=dp[i-1][j-1];
			else	dp[i][j]=min(min(dp[i-1][j],dp[i-1][j-1]),dp[i][j-1])+1;
		}
	}
	return dp[lena][lenb];
}
int main()
{
	scanf("%s%s",&a,&b);
	printf("%d\n",LCS());
	return 0;
} 

说了最长公共子串,接下来我们谈谈最长上升子序列,也是LCS的扩展;

LIS最长上升子序列

题目:

给定一个数组大小n,随后跟着n个数,问这n个数中最长上升子序列长度为多少;
例如:
5
3 5 1 2 4
假如说我们用LCS来做,先给这个数组从小到大排序;
3 5 1 2 4
1 2 3 4 5
他们的长公共子序列为1 2 4 ,所以我们可以这样来求;

代码:
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=1e3;
int a[MAXN],b[MAXN];
int dp[MAXN][MAXN];
void LCS(int n)
{
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			dp[i][j]=a[i]==b[j]?dp[i-1][j-1]+1:max(dp[i-1][j],dp[i][j-1]);
}
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)	scanf("%d",&a[i]),b[i]=a[i];
	sort(b+1,b+n+1);
	LCS(n);
	printf("%d\n",dp[n][n]);
	return 0;
}

然后我们来分析一下复杂度,空间复杂度为n²,时间复杂度也为n²,那我们可不可以优化一下哎,当然是可以的;接下来就是优化算法。

代码(空间:n,时间:n²):
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=1e5;
int arr[MAXN],dp[MAXN];
int LIS(int n)
{
	int ans=1;
	dp[0]=1;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<i;j++)
			if(arr[i]>arr[j])
				dp[i]=max(dp[i],dp[j]+1);
		ans=max(ans,dp[i]);
	}
	return ans;
}
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++)	scanf("%d",&arr[i]);
	printf("%d\n",LIS(n));
	return 0;
}
代码(空间:n,时间:nlongn):
#include<cstdio>
using namespace std;
const int MAXN=1e5;
int arr[MAXN],lis[MAXN];//lis 代表最长上升子序列长度 
int len;
int _find(int x)
{
	int left=0,right=len;
	while(left<right)
	{
		int middle=(left+right)>>1;
		if(lis[middle]<x)	left=middle+1;
		else	right=middle;
	}
	return left;
}
int main() 
{
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++)	scanf("%d",&arr[i]);
	lis[len]=arr[0];
	for(int i=0;i<n;i++)
	{
		if(arr[i]>lis[len])
			lis[++len]=arr[i];
		else
		{
			int k=_find(arr[i]);
			lis[k]=arr[i];
		}
	}
	printf("%d\n",len+1);
	return 0;
} 

这个算法借助到了二分的知识来找到第一个比它大的数,lis 数组里的数代表的是最长上升子序列长度的最后一个数,数组长度就是最长上升子序数的长度(注意,lis数组里的数不是最长上升子序数的所有数)。两种情况,如果这个数比lis数组里的数大,直接加入lis数组,len++;否则二分找他第一个比它大的数,替换它。

发布了39 篇原创文章 · 获赞 1 · 访问量 572

猜你喜欢

转载自blog.csdn.net/qq_45249273/article/details/104347962