最长上升子序列

给定一个长度为N的数列,求数值严格单调递增的子序列的长度最长是多少。
输入格式
第一行包含整数N。
第二行包含N个整数,表示完整序列。
输出格式
输出一个整数,表示最大长度。
数据范围
1≤N≤1000,
−109≤数列中的数≤109
输入样例:
7
3 1 2 1 8 5 6

输出样例:
4

上图

这里的话就开个一维数组,f[i]表示所有以第i个数结尾上升子序列
代码:

# include <iostream>
# include <cmath>
using namespace std;
const int N = 1000;
int a[N+10];
int dp[N+10];
int main(void)
{
    int n;
    cin>>n;
    int ans=0;
    for(int i=1;i<=n;++i)
        cin>>a[i];
    for(int i=1;i<=n;++i){
        dp[i]=1;
        for(int j=1;j<i;++j){                           //循环遍历【i,j】之间的dp[]值
            if(a[j]<a[i])
            dp[i]=max(dp[i],dp[j]+1);
        }
        ans=max(ans,dp[i]);
    }
    cout<<ans<<endl;
    /*
    //这个是求出最长上升子序列的代码
    int idx = -1;
    for(int i=1;i<=n;++i){   //先找出最长上升子序列的最后一个的下标
        if(dp[i]==ans){
            idx = i;
            break;
        }
    }
    int len = ans;
    int b[10000]={0};
    b[len--] = a[idx];
    for(int i=idx;i>1;--i){  //以这个为边界,进行向前逆向解答即可。
        if(a[i-1]<a[idx] && dp[i-1]==dp[idx]-1){   //因为答案肯定在左边的序列中
            b[len--] = a[i-1];
            idx = i-1;
        }
    }
    for(int i=1;i<=ans;++i){
        cout<<b[i]<<' ';
    }cout<<endl;
    */
    return 0;
}

在这里插入图片描述

这里的话是DP做法,时间复杂度对于1000还可以,那么问题来了,如果是100000怎么做?(用贪心算法)

思路:
f[i]数组表示长度为i的上升子序列,这里的话用下标表示,也就是说每个值都是唯一的,比如下标为3的就表示长度为3的上升子序列,有且仅仅保留一种最佳的长度为3的上升子序列;
f[]数组存的是每个长度上升子序列的最后一个值,即i等于3的话,f[3]存的就是长度为3的最佳上升子序列的末尾的元素

首先数组a中存输入的数(原本的数),开辟一个数组f用来存结果,最终数组f的长度就是最终的答案;假如数组f现在存了数,当到了数组a的第i个位置时,首先判断a[i] > f[cnt] ? 若是大于则直接将这个数添加到数组f中,即f[++cnt] = a[i];这个操作时显然的。
当a[i] <= f[cnt] 的时,我们就用a[i]去替代数组f中的第一个大于等于a[i]的数,因为在整个过程中我们维护的数组f 是一个递增的数组,所以我们可以用二分查找在 logn 的时间复杂的的情况下直接找到对应的位置,然后替换,即f[l] = a[i]。
我们用a[i]去替代f[i]的含义是:以a[i]为最后一个数的严格单调递增序列,这个序列中数的个数为l个。
这样当我们遍历完整个数组a后就可以得到最终的结果。
时间复杂度分析:O(nlogn)

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 5;
int a[maxn], f[maxn];
int cnt;

inline int find(int x) {
    int l = 1, r = cnt; 
    while(l < r) {
        int mid = l + r >> 1;
        if(f[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return l;
}

int main(void) {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
    f[++cnt] = a[1];

    for(int i = 2; i <= n; i ++) 
        if(a[i] > f[cnt]) f[ ++ cnt] = a[i];      //如果大于数组末尾元素,那么表示现在最长上升子序列应该是
        else {                                          //cnt+=1
            int tmp = find(a[i]);                 //否则就二分,然后更新末尾元素值
            f[tmp] = a[i]; 
        }

    printf("%d\n", cnt);
    return 0;
}

求出最长的上升子序列:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 5;
int dp[maxn];
int a[maxn], f[maxn];
int b[maxn];
int cnt;

inline int find(int x) {
    int l = 1, r = cnt; 
    while(l < r) {
        int mid = l + r >> 1;
        if(f[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return l;
}

int main(void) {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
    f[++cnt] = a[1];
    dp[1] = 1;
    
    for(int i = 2; i <= n; i ++) 
        if(a[i] > f[cnt]) {
            f[ ++ cnt] = a[i]; 
            dp[i] = cnt;
        }     //如果大于数组末尾元素,那么表示现在最长上升子序列应该是
        else {                                          //cnt+=1
            int tmp = find(a[i]);                 //否则就二分,然后更新末尾元素值
            f[tmp] = a[i]; 
            dp[i] = tmp;
        }

    printf("%d\n", cnt);
    
    int ans = cnt;
    int idx = -1;
    for(int i=1;i<=n;++i){
        if(dp[i]==ans){
            idx = i;
            break;
        }
    }
    int len = ans;
    int b[10000]={0};
    b[len--] = a[idx];
    for(int i=idx;i>1;--i){
        if(a[i-1]<a[idx] && dp[i-1]==dp[idx]-1){
            b[len--] = a[i-1];
            idx = i-1;
        }
    }
    for(int i=1;i<=ans;++i){
        cout<<b[i]<<' ';
    }cout<<endl;
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值