13.最长公共子序列

这篇博客主要介绍了如何使用动态规划解决最长公共子序列(LCS)问题。首先,区分了子串和公共子序列的概念,接着通过状态转移方程解释了动态规划的解题思路,并给出了两段C++代码实现,包括基础版和优化版。基础版的时间复杂度为O(n^2),而优化版利用序列特性将时间复杂度降低到O(n*logn)。
摘要由CSDN通过智能技术生成

【模板】最长公共子序列 - 洛谷

视频讲解:LCS_哔哩哔哩_bilibili 


解题思路:

1.比较两个序列,寻找公共子序列,在这里要区分公共子序列和子串的区别:

   子串是必须连续的,比如s1="abcde"     s2="abc"     s3="abde" 可以说s2是s1的子串,但是s3却     不是s1的子串

   公共子序列是不必连续的,但是得满足位置要求,比如s1和s3的公共子序列为abde

2.搞明白这两者的差别,接下里分析如果有两个序列,现在每个序列都去掉最后一个元素,求新序列的最长公共子序列的话,结果并不会对原序列产生影响,原序列只是在新序列公共长度的基础上多加了一组元素,然后比对一下,如果这组元素相等,那么在前一个序列上加1,表示此时序列的公共长度加1,如果不相等,则考虑继承,所以满足最优子结构并且无后效性,利用动态规划来解

3.设置状态:利用dp【i】【j】来表示第一个序列(1-i)的长度和第二个序列(1-j)的长度的最长公共子序列。

4.状态转移:如果此时的a[i]==b[j]的话,那么dp[i][j]=dp[i-1][j-1]+1;即在(1-i-1)的长度和(1-j-1)的长度的基础上加1,因为又出现了一对相同的元素,否则的话,dp[i][j]=max(dp[i-1][j],dp[i][j-1])

4.初始化:很明显,当dp[0][j]都为0,因为第1个序列为空,当dp[i][0]也为0,因为第二个序列为空

5.接下来就是遍历填表,输出dp【i】【j】


#include<bits/stdc++.h>
using namespace std;
int a[1010],b[1010];//序列1和序列2 
int dp[1010][1100];//dp数组 
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>a[i];//输入序列1 
	
	for(int i=1;i<=n;i++)
	cin>>b[i];//输入序列2 
	
	for(int i=0;i<=n;i++)
	dp[0][i]=0;
	for(int i=0;i<=n;i++)
	dp[i][0]=0;//dp初始化(实际没必要) 
	
	for(int i=1;i<=n;i++)//枚举序列1的每一个元素 
	{
		for(int j=1;j<=n;j++)//枚举序列2的每一个元素 
		{
			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]);//考虑继承 
		}
	}
	cout<<dp[n][n];//输出最长公共子序列 
	return 0;
}

算法优化:

1.不难看出,上述算法其实是在暴力枚举,时间复杂度为O(n*n),当数据为10^5时,超时。

2.题目中有一句关键信息为两个序列都是有1-n组成,即元素都相等,只是排列的方式不相等,如果把序列1看成一个本身就是单调递增的序列那么求两个序列的公共子序列也就变成求序列2的最长上升子序列!

3.例如:题目中序列1:   3 2 1 4 5,如果改为a,b,c,d,e

              即3-a,2-b,1-c,4-d,5-e,那么序列2(1,2,3,4,5)也就变成了c,b,a,d,e

              序列2最长上升子序列长度为3(a,d,e)对应的数字是(3,4,5)即两个序列的LCS

4.那么如何变化呢?可以利用一个map数组和桶排序的方法,将每个数字变成的数字映射到数组中,即map[a[i]]=i;比如当i为1的时候 a[1]=3,map[a[1]]=1,意思是3这个数字变成了1。

5.然后将序列2也按照上述规则变化,利用贪心思想将dp数组的元素值设置成末尾最小值(参考最长上升子序列问题),这样,空间由N^2压缩为一维,时间由N^2压缩为n*logn

#include<bits/stdc++.h>
using namespace std;
int a[100010],b[100010],mapp[100010],dp[100010];
int main()
{
	int n;
	cin>>n;
	
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		mapp[a[i]]=i;//设置变化规则 
	}
	for(int i=1;i<=n;i++)
	cin>>b[i];
	
	dp[1]=a[1];//dp初始化 
	int len=1;//记录最长上升子序列的长度 
	
	for(int i=2;i<=n;i++)
	{
		int low=0,high=len,mid;//设置二分查找的上下限 
		
		if(mapp[b[i]]>dp[len])//如果该数字比当前LIS的末位置大,说明又增加了一个长度 
		dp[++len]=mapp[b[i]];//加长并将这个数放入加长的位置 
		else
		{
			while(low<high) 
			{
				mid=(low+high)/2;
				if(dp[mid]>mapp[b[i]])
				high=mid;
				else
				low=mid+1;
			}
			dp[low]=min(dp[low],mapp[b[i]]);
		}
	}
	cout<<len;
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值