动态规划(DP)---- 子序列

在做动态规划的相关题,我们会遇到最长公共子序列,最长递增子序列,最长递增公共子序列等相关类型题,那么本期内容将围绕其展开讨论。

一.最长公共子序列

我们都知道动态规划的思想可以求解极值问题,通过对于其数组内参数不断的递推结合根据其dp数组的含义所推出的状态转移方程得到所求解,那么咱们根据动态规划如何求解子序列问题呢?

首先给出例题:

首先给出X = {1,2,3,6,2,3},Y = {1,2,6,3,1,2} 这两个序列,请你求得X和Y的一个最长公共子序列的长度。

首先分析这道题的时候咱们要注意这是一个线性DP问题,这道题可以暴力法枚举所有X的子序列并验证其是否是Y的子序列,很显然其复杂度会O(2^(m + n)),而利用动态规划对这两个序列进行遍历,就是两个for循环的嵌套,其复杂度为O(nm)。

咱们先研究dp数组的含义,这道题我们不妨设dp[i][j]数组,其含义为遍历到X对应的前i个元素与Y对应的前j个元素的最长公共子序列的长度。

那么假设0代表不取,1代表取,那么其集合就可以表示为:01,10,11

如果Xi = Yj(11) ,那么可以推出状态转移方程为:dp[i][j] = dp[i - 1][j - 1] + 1;

如果Xi ≠ Yi(01或10),那么:dp[i][j] = max(dp[i - 1][j] , dp[i][j - 1]);

#include 
using namespace std;
const int N = 1000;
int a[N],b[N];
int n;
int dp[N][N];
int main()
{
	cin >> n ;
	for(int i = 1;i <= n;i++)
	{
		cin >> a[i] >> b[i];
	}
	for(int i = 1;i <= n;i++)
	{
		for(int j = 1;j <= n;j++)
		{
			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;
}

二.最长递增子序列

 给定一个长度为n的数组a[]= {5,6,7,4,2,8,3},请你求出他的最长递增子序列的长度。

 在处理这道题的时候我们应该思考如何保持让其记录并保证递增?

首先dp[i]数组的含义为前i个元素的最长递增子序列的长度,我们不妨设一个k来遍历前i个元素

如果a[k] < a[i],那么更新dp[i]的最大值:dp[i] = max(dp[i],dp[k] + 1);

#include 
using namespace std;
const int N = 1000;
int a[N];
int dp[N];
int main()
{
	int imax = 1;
	for(int i = 0;i <= 9;i++)  cin >> a[i];
	for(int i = 0;i <= 9;i++)
	{
		dp[i] = 1;
		for(int k = 0;k <= i;k++)  //筛选当前i值的递增子序列的最大值
		{
			if(a[i] > a[k])  //若k < i,则满足递增条件
			{
				dp[i] = max(dp[i],dp[k] + 1);  //dp[k]存放的是前k个元素筛选的最大值,+1加的是a[i]这个成立的情况,不断筛选使得最后筛选出的dp[i]值最大
			}
		}
		imax = max(dp[i],imax);//筛选出每个前i个元素的最大数中的最大值
	}
	cout << imax;
    return 0;
}

三.最长递增公共子序列

在讲述了前两个后我们可以把上面两种求解方式所结合在一起。

首先定义dp[i][j]数组表示含义为遍历到X对应的前i个元素与Y对应的前j个元素的最长公共递增子序列的长度.

我们将其分成选a[i]与不选a[i]

如果不选a[i],那么其dp[i][j] = dp[i - 1][j];

如果要选择a[i],那么如果a[i] == b[j],分析如何使其递增,根据上面所讲到的最长递增子序列,设置第三方参数k来遍历b数组内前j个元素内的子序列判断是否存在递增序列,如果存在则与前面所筛选的最大值来比较得出在这种情况的最值。

#include 
using namespace std;
const int N = 1000;
int a[N],b[N];
int dp[N][N];
int main()
{
	for(int i = 0;i <= 6;i++) 
	{
		cin >> a[i] >> b[i];
	}
	for(int i = 0;i <= 6;i++)
	{
		for(int j = 0;j <= 6;j++)
		{
			if(a[i] == b[j])  //选择a[i]的集合 
			{
				int maxv = 1;
				for(int k = 0;k <= j;k++) //遍历前j个元素的递增情况
				{
				    if(b[k] < b[j])  //如果k < j,则满足情况
				        maxv = max(maxv , dp[i - 1][k] + 1);筛选当前j值的最大值,dp[i - 1][k]是代表b数组的前k个元素的最大值,最后筛选出选a[i]情况下的前j个元素的最值来存入maxv中
				}
			    dp[i][j] = max(dp[i - 1][j],maxv);    //比较选与不选a[i]
			}
		}
	}
	int res = 0;
	for(int i = 0;i <= 6;i++)  res = max(res , dp[6][i]);//比较i = 6时候的每个j值所对应的dp数组的max
	cout << res;
	return 0;
}

 好了,今天的内容就到这里了,感谢收看,记得三连支持,后序会继续更新算法的相关知识。

你可能感兴趣的:(动态规划,动态规划,算法)