最长公共子序列(LIS)(线性动态规划)

些许的灵感:我们可以从dp数组的最后一位开始考虑 ,看看这个是怎么从前面推来的,这样就方便发现子问题。如果知道子问题的值,那么就好办了。

动态规划更多的是一种递推的思想(从后往前推,这样可以避免递归)

动态规划的状态转移方程就是由三个词决定的:选定   继承  优化

每个数组元素都是我们要的答案(一般情况来说)。

关于数据结构:对于这种有两个线性结构的来说一般是采用二维数组(线性动态规划一般使用二维数组)。

最优情况下考虑创造值。

非最优情况下考虑继承(而且是在继承中取最优)。

子问题就是相当于初始你是怎么想的。追溯到子问题那么这个问题就会结束。

为什么我们要用一个二维数组来存数据:i代表s1串,j代表s2串,每一个dp数组所存的元素就是lis的长度(就是我们最后需要的答案,那么数组的最后就是我们呢需要的答案)

为什么相等的时候就要取dp[i-1][j-1]+1?:因为dp[i-1][j-1]就是我们之前算过的最长公共子序列,由于已经满足了这两个相等,所以要向上一个取最优解。

为什么不相等的时候要去max(dp[i-1][j],dp[i][j-1])?:由于我们没有发现相等的,所以要么向s1前找最优解,要么向s2找最优解。i-1就是相当于向s1前面找最优解,j-1相当于向s2找最优解。

在线性动态规划中选取数字这个操作很重要,就是以这个数字为标准(这句话我说出来可能比较抽象)。

下面这张图方便理解

模板】最长公共子序列

 

dp数组的含义:下标i代表前i个数字(包括i),下标j代表前j个数字(包括j) ,dp数组所存的数字就是长度为i和j的时候的lis长度。

动态规划代码(时间会爆)

#include<iostream>
#include<cstdio>
using namespace std;
const int N=1e5+100;
int s1[N],s2[N];
int dp[100][100];
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)cin>>s1[i];
	for(int i=1;i<=n;i++)cin>>s2[i];
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(s1[i]==s2[j])
			{
				dp[i][j]=dp[i-1][j-1]+1;
			}
			else
			{
				dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
			}
		}
	}
//	for(int i=1;i<=n;i++)
//	{
//		for(int j=1;j<=n;j++)
//		{
//			cout<<dp[i][j]<<" ";
//		}
//		cout<<endl;
//	}
	cout<<dp[n][n]<<endl;
	return 0;
}

 编辑距离

做了前面那道题这一道题就不是很难理解了(虽然还是很难理解)

dp数组的含义:

下标i就是s1的前i个字符(包括i),下标i就是s2的前j个字符(包括j)。

对于dp数组里面存的数字就是我们要的答案:所要进行的最少操作

 在前面的灵感中我就已经说过,动态规划其实就是一种递推的过程,所以我们一般从从往前的思路进行思考,我们们选定最后一个字符进行继承优化。

子问题看似是三个其实是四个:

首先就是三个题目给的(详细在注释中认真看注释)

//			//插入操作就是相当于s1的前i个字符和s2的j-1个字符具有高度相似性,所以可以继承前面的
			dp[i][j]=dp[i][j-1]+1;
//			//删除操作就是相当于s1的前i-1个字符和s2的前j个字符具有高度相似性,所以可以继承前面的
			dp[i][j]=dp[i-1][j]+1;
//			//替换操作就是s1的前i-1个字符和s2的前j-1个字符具有高度相似性,所以可以继承前面的
			dp[i][j]=dp[i-1][j-1]+1;

最后一个操作就是如果相同就不需要进行任何操作,将i-1和j-1前面的操作步数直接继承过来就行了

	dp[i][j]=min(min(dp[i][j-1]+1,dp[i-1][j]+1),dp[i-1][j-1]+1);

那我们如何对删减 替换 增加呢?

其实不用想这么多,那个步数少就继承那个,所以直接用min函数将这三种情况选择最小的即可

	dp[i][j]=min(min(dp[i][j-1]+1,dp[i-1][j]+1),dp[i-1][j-1]+1);

完整的代码如下:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<string.h>
using namespace std;
char s1[2010];
char s2[2010];
int dp[2010][2010];
int main()
{
	scanf("%s",s1+1);
	scanf("%s",s2+1);
	int len1=strlen(s1+1);
	int len2=strlen(s2+1);
	dp[0][0]=0;
	for(int j=1;j<=len2;j++)
	{
		dp[0][j]=j;
	}
	for(int i=1;i<=len1;i++)
	{
		dp[i][0]=i;
	}
	for(int i=1;i<=len1;i++)
	{
		for(int j=1;j<=len2;j++)
		{
			//继承,不需要进行任何操作
			if(s1[i]==s2[j])dp[i][j]=dp[i-1][j-1];
			if(s1[i]!=s2[j])
			dp[i][j]=min(min(dp[i][j-1]+1,dp[i-1][j]+1),dp[i-1][j-1]+1);
			
//			//插入操作就是相当于s1的前i个字符和s2的j-1个字符具有高度相似性,所以可以继承前面的
//			dp[i][j]=dp[i][j-1]+1;
//			//删除操作就是相当于s1的前i-1个字符和s2的前j个字符具有高度相似性,所以可以继承前面的
//			dp[i][j]=dp[i-1][j]+1;
//			//替换操作就是s1的前i-1个字符和s2的前j-1个字符具有高度相似性,所以可以继承前面的
//			dp[i][j]=dp[i-1][j-1]+1;
		}
	}
//	for(int i=1;i<=len1;i++)
//	{
//		for(int j=1;j<=len2;j++)
//		printf("%d ",dp[i][j]);
//		cout<<endl;
//	}
	cout<<dp[len1][len2]<<endl;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白色的风扇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值