线性DP:最长上升子序列

什么是最长上升子序列?

这个最长上升子序列,有的叫最长递增子序列,有的叫最长不下降子序列(这个可能有点区别,相当于可以有相等的情况) 而最长上升子序列是每个数逐渐递增

这里的子序列可以不连续 但是要是递增的

举个例子

1 2 4 1 3 4

这里最长的上升子序列为:1 2 3 4

如何求最长上升子序列?

算法核心思想:

1设置一个一维数组dp[i]:表示以a[i]结尾的最长上升子序列(数组下标这里从1开始)

2依次比较定义个J遍历1-i之间的所有情况

 具体步骤:若a[j]<a[i] 

                   那么以a[i]结尾的最长上升子序列等于以a[j]结尾的上升子序列+1

                   即:dp[i]=max(dp[j],dp[i]);

3最后我们需要再dp[i]中选取一个最大的 就是最长上升子序列的长度

举个例子

1 2 4 1 3 4

dp[1]=1 因为当序列只有一个数的时候最长为1

dp[2]=1+1=2 由于1<2 所以dp[2]的最长上升子序列等于dp[1]+1;

dp[3]=2+1 

这里需要比较两次 我们每次都是从第一个数比较到i-1个数

1<4 所以dp[3]=dp[1]+1=2

2<4 所以dp[3]=dp[2]+1=3

当然这里不是每次都更新 我们要保留最大的 所以要dp[3]=max(dp[3],dp[j]); j从1-2 

举个反例:2314 

当我们求4结尾的手 dp[4]=dp[2]+1=3;

但后面由于1比2 3都小 以1结尾的子序列只有自己一个 所以dp[3]=1

而若直接更新 dp[4]=dp[3]+1=2 

显然是错的 前面那种情况更大 所以我们在与前面比较的时候需要取最大值

随后dp[4]=1

dp[5]=3; 123 和 12我们取最大值dp[2]转移过来的 而不是dp[4]转移过来的

dp[6]=4 即子序列1234 是由dp[5]转移过来的 因为3<4 而dp[5]又是前面最大的一个

由于部分题目需要求出最大长度和具体序列 

所以我们需要开辟一个数组g,记录dp[i]由那个状态转移过来的 其实就是在每次max更新的时候记录接在谁后面

#include <iostream> 
using namespace std;
const int N=210;
int a[N],f[N],g[N],s[N];
int main()
{
	int n,ans=-1;
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	
	for(int i=1;i<=n;i++)
	{
		f[i]=1;
		for(int j=1;j<i;j++)
		{
			if(a[j]<=a[i]) //前一个比我小才可以讨论 
				{
					if(f[i]<f[j]+1) //新方案更大才更新  
					{
					f[i]=max(f[i],f[j]+1);
					g[i]=j;//记录状态由哪里转移过来 只有更新了才记录 所以再if循环内 
					}
				}
		}
	}
	int k=0;
	for(int i=1;i<=n;i++)//这一步是为了求出最长上升子序列是哪一个结尾的 记录他的下标
	{
		if(f[i]>ans)
		{
			ans=f[i];
			k=i;
		}
	}
	printf("max=%d\n",ans);
	for(int i=0;i<ans;i++)
		{
			s[i]=a[k];//将序列每个数字读入数组 因为有些题目有输出顺序
			k=g[k];//更新g[k]表示dp[k]是的上一个数的下标 然后我们让k=g[k] 
		}
		for(int i=ans-1;i>=0;i--)cout<<s[i]<<' ';
	
	return 0;
}

                    

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值