最长上升子序列

朴素版本:895. 最长上升子序列 - AcWing题库

AC代码:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1010;
int arr[N];
int f[N];
int main()
{
    int n;cin>>n;
    for(int i=1;i<=n;i++)cin>>arr[i];
    
    for(int i=1;i<=n;i++)
    {
        f[i]=1;
        for(int j=1;j<=i-1;j++)
        {
            if(arr[i]>arr[j])
            f[i]=max(f[i],f[j]+1);
            
        }
    }
    int res=0;
    for(int i=1;i<=n;i++)res=max(res,f[i]);
    cout<<res;
    return 0;
}

相关解释:

因为这里的数据范围是1000,那么双重暴力可以解决这个问题。接下来详细来讲讲数据范围比较大的时候应该如何去进行优化。

优化版本:896. 最长上升子序列 II - AcWing题库

AC代码:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N =100010;
int arr[N];
int f[N];//f[i]表示上升序列长度为i的最小的结尾
int res;
int main()
{
    int n;cin>>n;
    for(int i=1;i<=n;i++)cin>>arr[i];
    memset(f,0x3f,sizeof f);
    f[0]=-2e9;
    for(int i=1;i<=n;i++)
    {
        int p=lower_bound(f,f+n+1,arr[i])-f;
        f[p]=arr[i];
        res=max(res,p);
    }
    cout<<res;
    return 0;
}

相关解释:

先声明一下,以下思路不是完整的思路,我说的不是很清楚.

首先这里的数据范围是100000,所以双重暴力肯定会tle,所以我们必须去进行一个优化。这里的话,我们就要改变一下状态的表示。f[i]表示的是上升序列长度为i的结尾的最小值。这里的话,好像不能进行状态的转移。那么这种状态的表示有什么好处呢?我们可以思考一下,假设我们这里遍历到了arr[i],此时要算出以它为结尾的最长上升子序列的长度,那么它是不是要加在在它以前的数为结尾的子序列的后面,并且只能加在比它小的数的后面,并且这个子序列是所有它以前的数为结尾的子序列的一个最长的,那么加上去之后以它为结尾的子序列是最长的。

那么这里我们可以想象一下我们这个状态的表示,f[i]表示的是上升序列长度为i的结尾的最小值,并且它满足一个性质:这里的f数组里面它是严格递增的。你想象一下,如果f[6]<=f[5],也就是长度为6的一个上升序列的最小结尾要小于等于长度为5的一个上升序列的最小结尾。因为这里的f[6]是根据f[5]来的,有一点动态规划的思想,所以它的一个最小结尾是根据大于长度是5的一个最小结尾的所有数的一个最小值。所以这里的f数组是严格递增的。那么这样有什么好处呢?这样是不是正好符合二分啊!!!

这里想象一下一个始终是递增的序列,刚开始为空,那么现在我们依次遍历arr数组,每遍历到一个数,如果这个数大于这个序列的最后一个数(因为这里的序列是递增的),那么我们就将这个数放在这个序列的最后。如果这个数小于最后一个数,我们在这个序列中找到第一个大于或等于这个数的数,然后替换它,这样保证序列是《最有潜力》,也就是1,3,6,没有1,3,4,潜力好,因为5可以接在后者的后面而不能接在前者的后面。这样找是不是符合二分,这样的做法是不是可以保证这个序列始终是递增的。这个序列就是我们的f数组!!!

这里我们可以想象一下,如果找到一个很大的数,那么序列就会加1,这个数很小我们也只是替换,它也不会增加只是替换,所以也不会影响最终的答案。

我么可以使用STL的lower_bound函数在f数组里面找到第一个大于或等于它的位置,因为它是大于或等于当前遍历到的这个数,所以我们就要去替换它,因为f数组的定义是长度为i的上升序列的一个最小的结尾,我们用lower_bound找到这个位置,因为这个数一定加在比他小并且是最长的一个序列的后面,注意一定是最长的一个序列,那么我么这里的f数组刚刚证明了是严格单调递增的。直接找到一个长度最大的并且结尾要比他本身要小的位置,那么这个位置的下一个位置所对应的f数组要大于或者等于当前遍历到的这个数,所以就可以去更新这个位置的数,始终是上升子序列长度为i的结尾的一个最小值。所以这里为什么要用lower_bound函数。

注意初始化一定要注意,所以最后的答案就是最后数组f里被更新的数的个数。可以遍历也可以使用max函数每次更新一下。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值