解题思路:

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])

hive 公共子序列 最长公共子序列视频_公共子序列


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;
}