最长单增子序列


最近看到了一个很短的方法,可以让你免于写二分查找,因此贴出来,另外顺便介绍一下这个问题的解决方法;


大致问题描述如下:


输入一个n代表有n个数字,然后输入n个数字(-10^9<=n[i]<=10^9),然后求这n个数字里面的最长单增子序列;



首先说一说思路(复杂度为n*log(n)的思路)

假设原序列为A,最终目标序列为B 。顺序考虑A里面的每一位元素,如果这个元素比B里面的每一位都大,那么就把它放到最后,否则让它替换大于或等于它的中最小的一个数(比如假设B中目前为3 4  6 8,而新数字为5,那么就让它去替换6,如果B中为3 4 5 8,那么就让它去替换5,结果仍然是3 4 5 8 [这样是为了防止同一数字多次被排列,从而出现如2 2 2 2的答案]),当考虑完A中所有的数,算法结束。

然后说一下代码实现方案:

首先初始化B的每一位值都为10^9+1(通常我们用dp来代表B),然后通过二分查找B数组当前长度的方式确定A[i]应该替换哪一个数字(当替换的是10^9+1时就代表把这个数加到了最后嘛)

最后说一下原理:

对于A来说它的排列是无规则的,那么我们需要考虑一个问题:子序列的最大值到底是多少?如果找到最大值,那么基本就相当于确定了子序列的最后一位,我们期望的状态是什么样子的呢?是:当找到最大的一位时倒数第二位...倒数第三位...倒数第四位.......最小的一位都已经被我们找到了。那么我们就可以尝试直接顺序考虑A,当碰到递增的往B里面放就行了,但是如果碰到了不递增的怎么办呢?不理它么?那万一A是这样的怎么办?A=4 5 1 2 3;当我们处理到第二个数据时,B的最大值是5,长度是2,我们可以想:如果我们能在A没有被我们考虑完找到一个能和它一样长并且最大值要小于它的序列多好。那么该怎么找呢?方法就是让目前考虑的A[i]替换大于或等于它的中最小的一个数。这样,就解决了,达到了我们上面要求的目的了。

在这里留给读者两个思考题:

1 倘如A=4 5 6 1 2 n时无论n值是多少是否使用上面的算法是否都不会影响最终答案?

2 这种算法能否打印最长子序列?

最后附代码:

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
#define inf 1000000001
using namespace std;
int main(){
    int n;
    cin>>n;
    int num[n+1],dp[n+1];
    fill(dp,dp+n+1,inf);
    memset(num,0,sizeof(num));
    for(int i=1;i<=n;i++)scanf("%d",&num[i]);

    for(int i=1;i<=n;i++) *lower_bound(dp+1,dp+n+1,num[i])=num[i];
    cout<<lower_bound(dp+1,dp+1+n,inf)-dp-1<<endl;

}
简单介绍一下lower_bound(m,m+n,a)的用法,他的意思判断a这个数据最小能插入数组m~m+n 这一段哪个位置并返回它的位置。另外读者有兴趣可以搜索upper_bound 的用法

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值