动态规划之最长上升子序列模型

最长上升子序列:

给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。

例如:3 1 2 1 8 5 6这个序列的最长递增子序列长度为4(1 2 5 6)。

输入格式:

第一行包含整数 N ;第二行包含 N 个整数,表示完整序列。

输出格式:

输出一个整数,表示最大长度。

数据范围:

1 <= N <= 1000 ,-1e9 < 序列元素 <1e9 。

思路:

假设序列起始位置为 1 ,集合 k 表示以 k 位置为结尾的上升子序列的集合,f[k] 为 k 集合中的上升子序列中的最长的上升子序列的长度;

由于以 k 位置结尾的上升子序列一定是由以 1 ~ k-1 位置结尾的上升子序列连接得到,故 k 集合可以划分为1、2……,k -1(1表示以 1 位置为结尾的上升子序列的集合,2 表示以 2 位置为结尾的上升子序列的集合…… k-1 表示以 k-1 位置为结尾的上升子序列的集合 );

由上述可知,f[i] = max( f[1] ~ f[i-1]) +1;

故可以从前往后枚举,每次枚举中查找 1 ~ i-1,若查找到的 f[j] < f[i],则 f[i] = max( f[i] , f[j] + 1 );

初始状态 f[i] = 1;

时间复杂度为O(n^2)。

代码模板如下:

#include<iostream>

using namespace std;
const int N = 1010;
int f[N], a[N];
int n, res = 1;

int main() {
    cin >> n;

    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        f[i] = 1;

        for (int j = 1; j < i; j++)
            if (a[j] < a[i]) {
                f[i] = max(f[i], f[j] + 1);
                res = max(res, f[i]);
            }
    }

    cout << res;
    return 0;
}

优化:

时间复杂度可优化至O( nlogn ),步骤如下:

从前到后遍历序列;

1. 给定一个单调递增栈;

2. 如果当前遍历到的元素大于栈顶元素,则将当前元素加入栈顶;

3. 如果当前元素小于等于栈顶,则在栈中查找第一个大于等于当前元素的元素,用当前元素替换掉找到的元素,二分查找时间复杂度O( logn ); 

栈的大小就是最长上升子序列的长度。

二分算法:二分算法_二分排序算法_如何何何的博客-CSDN博客

代码模板如下:

#include<iostream>
using namespace std;
const int N=1e5;
int q[N],g[N];
int n=1,res;

void check(int x){
	//找到第一个大于等于q[x]的 
	int l=0,r=res,mid;
	while(l<r){
		mid=(l+r)>>1;
		if(g[mid]>=q[x])r=mid;
		else l=mid+1;
	}
	g[l]=q[x];
}

int main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>q[i];
	//while(cin>>a[n++]);
	//n--;
	
	g[0]=q[1];
	for(int i=2;i<=n;i++){
		if(q[i]>g[res])g[++res]=q[i];
		else check(i);
	}
	
	//for(int i=0;i<res;i++)cout<<g[i]<<" "; 
	cout<<res+1;
	
}

例题1 . 怪盗基德的滑翔翼:

怪盗基德的滑翔翼 - C语言网 (dotcpp.com)

一个方向的最长下降子序列等于反方向的最长上升子序列;

求出从前往后和从后往前的最长上升子序列即可。

AC代码如下:

#include<iostream>
using namespace std;
const int N=110;
int f_l[N],f_r[N];//往左和往右的最长上升子序列
int a[N];

int main(){
    int T;
    cin>>T;
    while(T--){
        int n;
        cin>>n;
        for(int i=0;i<n;i++)cin>>a[i];
        
        int res=0;
        //正向最长上升子序列
        for(int i=0;i<n;i++){
            f_r[i]=1;
            for(int j=0;j<i;j++)
                if(a[j]<a[i])f_r[i]=max(f_r[i],f_r[j]+1);
            res=max(res,f_r[i]);
        }
        
        //反向最长上升子序列
        for(int i=n-1;i>-1;i--){
            f_l[i]=1;
            for(int j=n-1;j>i;j--)
                if(a[j]<a[i])f_l[i]=max(f_l[i],f_l[j]+1);
            res=max(res,f_l[i]);
        }
        cout<<res<<endl;
        
    }
    return 0;
}

例题2 . 登山:

登山 - C语言网 (dotcpp.com)

假设 f[i] 为以 i 号山为最高山时,能游览的山的最大数量;

f[i] 为以 i 点的山为终点左侧序列的最长上升子序列的长度,加上以 i 点的山为起点右侧最长下降子序列的长度,再减去 1 ;

求最长下降子序列即反向求最长上升子序列。

AC代码如下:

#include<iostream>
using namespace std;
const int N=1010;
int f_l[N],f_r[N];//往左和往右的最长上升子序列
int a[N];

int main(){
    int n;
    cin>>n;
    for(int i=0;i<n;i++)cin>>a[i];
        
    //正向最长上升子序列
    for(int i=0;i<n;i++){
        f_r[i]=1;
        for(int j=0;j<i;j++)
            if(a[j]<a[i])f_r[i]=max(f_r[i],f_r[j]+1);
    }
        
    //反向最长上升子序列
    for(int i=n-1;i>-1;i--){
        f_l[i]=1;
        for(int j=n-1;j>i;j--)
            if(a[j]<a[i])f_l[i]=max(f_l[i],f_l[j]+1);
    }
    
    int res=0;
    for(int i=0;i<n;i++)res=max(res,f_l[i]+f_r[i]-1);
    
    cout<<res;
    return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值