【动态规划】LIS最长上升子序列和LCS最长公共子序列

前言—子序列定义

在学习LIS和LCS之前,我们需要了解子序列的定义:假设我们定义一个长度为n的数组a[n],那么我们从数组中拿出m(不大于n)个元素,按照这些元素在原数组中排列的顺序排列成一个新的数组,我们可以说这个新的数组为原数组的子序列数组。

例如:a[5]={1,2,3,4,5},那么b[2]={1,3,5},c[3]={2,4,5}…都是a的子序列数组。在这里要强调,子序列数组中元素在原数组中不一定要求是连续的。例如不一定只有b[3]={1,2,3}或者c[3]={3,4,5}才是a的子序列数组,在这里特别指出。

在介绍完子序列的定义后,我们开始讲解LIS和LCS。

一、 LIS—最长上升子序列

**定义:**在一个长为n的数组a[n]中,我们需要找到这样一个子序列满足以下两个条件。

  • 1.子序列的元素从左到右单调递增
  • 2.在所有满足条件1的子序列中,找出长度最长的那个子序列

这样的子序列就是最长上升子序列。

DP解法:
由于本篇博客篇幅有限且重点在于动态规划,因此在这里介绍DP解法。在DP解法中,我们需要用到两个数组----原数组a[n]和动态规划数组dp[n]。详细思路让我们在代码里讲解。

#include<iostream>
#include<algorithm>
#include<cmath>
#define ll long long
using namespace std;
typedef  pair<int, int> PII;

int n;
int a[1050];
int dp[1050];  //dp[i]代表的是以a[i]结尾的(一定选上了a[i])最长上升子序列的长度。
int ma = 0;  //ma存放最大值
void solve()
{
	cin >> n;   //n为数组长度
	for (int i = 1; i <= n; i++)
	{
		dp[i] = 1;//首先,我们把整个dp数组初始化为1,因为长度为1的只含自己本身的子序列是一定满足条件的。
		cin >> a[i];
	}
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j < i; j++)
		{
			if(a[j]<a[i])//对于每一个a[i],我们需要重新扫描a[i]到a[i-1],如果a[j]<a[i],看看a[i]加在以哪个数为终点的LIS后形成的新LIS最长
			dp[i] = max(dp[i], dp[j] + 1);//dp数组需要不断更新以确保能获得最长长度
		}
		ma = max(dp[i], ma);//更新最后答案
	}
	cout << ma;
}

int main()
{
	solve();
	return 0;
}

时间复杂度O(n^2),并不是最优解。贪心加二分与树状数组维护的复杂度都更低,但是在这里不细说了。

二、 LCS—最长公共子序列

*定义:**在一个长为n的数组a[n]与长为m的子序列b[m]中,我们需要找到这样一个c[n]满足以下两个条件。

  • 1.c[n]同时是a与b的子序列
  • 2.在所有满足条件1的子序列中,找出长度最长的那个子序列

这样的子序列就是最长公共子序列。
DP解法:
我们定义一个dp[n][m],其中dp[i][j]表示子序列a[i]与b[j]的LCS的长度
而我们可以得到如下状态转移公式

  • 1:if(i==0 并且 j==0) ---->dp[i][j]=0;
  • 2:else if(a[i]==b[j])---->dp[i][j]==dp[i-1][j-1]+1;
  • 3:else---->dp[i][j]==max(dp[i-1][j],dp[i][j-1]);
    在这里插入图片描述
    代码:
#include<iostream>
#include<string>
#include<algorithm>
#include<cmath>

#define ll long long
#define fi first
#define se second
using namespace std;
typedef  pair<int, int> PII;

string a, b;
int dp[1000][1000];
void solve()
{
	while (cin >> a >> b)
	{
		int la = a.size(), lb = b.size();
		memset(dp, 0, sizeof dp);
		if (la == 0 || lb == 0)
		{
			cout << "0" << endl;
			continue;
		}
		else
		{
			for (int i = 1; i <= la; i++)
			{
				for (int j = 1; j <= lb; j++)
				{
					if (a[i - 1] == b[j - 1])
						dp[i][j] = dp[i - 1][j - 1] + 1;
					else
						dp[i][j] =max(dp[i-1][j],dp[i][j-1]);
				}
			}
			cout << dp[la][lb] << endl;
		}
	}
}

int main()
{
	solve();
	return 0;
}

时间复杂度为O(n*m)。

作者:Avalon·Demerzel,如果本篇博客有帮到你就留下一个赞吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值