动态规划:最长上升子序列(二分算法 nlogn)「建议收藏」

动态规划:最长上升子序列(二分算法 nlogn)「建议收藏」解题心得:1、在数据量比较大的时候n^2会明显超时,所以可以使用nlogn的算法,此算法少了双重循环,用的lower_bound(二分法)。2、lis中的数字并没有意义,仅仅是找到最小点lis[0]和最大点lis[len],其中,在大于lis[len]时len++,在小于lis[len]时可以将arr[i]在lis中的数进行替换掉。所以此算法主要是在不停的找最合适的起点和最合适的终点。

大家好,又见面了,我是你们的朋友全栈君。

早些年间写的博客,当时对算法还不是很熟悉,所以写的很简略,也没说清楚,有不少留言提问,现在重新修改这个博客。

增添:(增添的内容看不懂的可以先看算法,然后在返回来看增添的内容)
首先明白两个二分函数:

  • l o w e r lower lower_ b o u n d ( s t a r t P o s , e n d P o s , v a l u e ) bound(startPos, endPos, value) bound(startPos,endPos,value):在区间 [ s t a r t P o s , e n d P o s ) [startPos, endPos) [startPos,endPos)内找到第一个大于等于 v a l u e value value的地址,如果找不到则返回 e n d P o s endPos endPos,注意的是区间左闭右开。
  • u p p e r upper upper_ b o u n d ( s t a r t P o s , e n d P o s , v a l u e ) bound(startPos, endPos, value) bound(startPos,endPos,value):在区间 [ s t a r t P o s , e n d P o s ) [startPos, endPos) [startPos,endPos)内找到第一个大于 v a l u e value value的地址,如果找不到则返回 e n d P o s endPos endPos,注意的是区间左闭右开。

而这个 n ∗ l o g n n*logn nlogn复杂度算法的思想是什么呢?就是维护一个递增数列,让数列整体的值尽量的小,这样当出现新的一个数的时候,能加进数列并且不改变数列递增性质的可能才更大。而这个数列本身是没有意义的,数列里面的数并不是你找到的上升子序列。

假设当一个新的数出现的时候有两种情况,第一种是这个数比递增数列中的最后一个数更大,那直接加入,数列的长度加一。第二种是这个数加入数列最后一位后改变了数列的递增性质,这个时候拿这个数替换数列中存在的第一个比它大的数(一定能找到),替换之后让数列总体尽可能的小并且没有改变数列的长度,所以符合无后效性,按照这样得到的答案是合法的。

所以使用 l o w e r lower lower_ b o u n d bound bound还是使用 u p p e r upper upper_ b o u n d bound bound都是一样的(可能写法不一样),是得到递增子序列还是不递减子序列是在最后加入的数是大于数列最后一个数还是大于等于数列最后一个数。

这个时候再多想一想,我们在遇到 n u m [ i ] num[i] num[i]这个数时,将这个数在数列中二分查找出来的那个位置有什么意义,那么记找到的这个位置为 p o s pos pos p o s pos pos代表的就是以 n u m [ i ] num[i] num[i]这个数为结尾的单调数列的最长长度,这个时候才需要区分到底是递增数列还是不递减数列来选择使用 l o w e r _ b o u n d lower\_bound lower_bound还是使用 u p p e r _ b o u n d upper\_bound upper_bound得到最长长度。
增添结束


解题心得:
1、在数据量比较大的时候 n 2 n^2 n2会明显超时,所以可以使用 n ∗ l o g n n*logn nlogn 的算法,此算法少了双重循环,用的 u p p e r b o u n d upper_bound upperbound(二分法)。
2、 l i s lis lis中的数字并没有意义,仅仅是找到最小点 l i s [ 0 ] lis[0] lis[0]和最大点 l i s [ l e n ] lis[len] lis[len],其中,在大于 l i s [ l e n ] lis[len] lis[len] l e n + + len++ len++,在小于 l i s [ l e n ] lis[len] lis[len]时可以将 a r r [ i ] arr[i] arr[i] l i s lis lis中的数进行替换掉。所以此算法主要是在不停的找最合适的起点和最合适的终点。


引用:
假设存在一个序列d[1…9] = 2 1 5 3 6 4 8 9 7,可以看出来它的LIS长度为5。n
下面一步一步试着找出它。
我们定义一个序列B,然后令 i = 1 to 9 逐个考察这个序列。
此外,我们用一个变量Len来记录现在最长算到多少了
首先,把d[1]有序地放到B里,令B[1] = 2,就是说当只有1一个数字2的时候,长度为1的LIS的最小末尾是2。这时Len=1

然后,把d[2]有序地放到B里,令B[1] = 1,就是说长度为1的LIS的最小末尾是1,d[1]=2已经没用了,很容易理解吧。这时Len=1

接着,d[3] = 5,d[3]>B[1],所以令B[1+1]=B[2]=d[3]=5,就是说长度为2的LIS的最小末尾是5,很容易理解吧。这时候B[1…2] = 1, 5,Len=2

再来,d[4] = 3,它正好加在1,5之间,放在1的位置显然不合适,因为1小于3,长度为1的LIS最小末尾应该是1,这样很容易推知,长度为2的LIS最小末尾是3,于是可以把5淘汰掉,这时候B[1…2] = 1, 3,Len = 2

继续,d[5] = 6,它在3后面,因为B[2] = 3, 而6在3后面,于是很容易可以推知B[3] = 6, 这时B[1…3] = 1, 3, 6,还是很容易理解吧? Len = 3 了噢。

第6个, d[6] = 4,你看它在3和6之间,于是我们就可以把6替换掉,得到B[3] = 4。B[1…3] = 1, 3, 4, Len继续等于3

第7个, d[7] = 8,它很大,比4大,嗯。于是B[4] = 8。Len变成4了

第8个, d[8] = 9,得到B[5] = 9,嗯。Len继续增大,到5了。

最后一个, d[9] = 7,它在B[3] = 4和B[4] = 8之间,所以我们知道,最新的B[4] =7,B[1…5] = 1, 3, 4, 7, 9,Len = 5。

于是我们知道了LIS的长度为5。
!!! 注意。这个1,3,4,7,9不是LIS,它只是存储的对应长度LIS的最小末尾。有了这个末尾,我们就可以一个一个地插入数据。虽然最后一个d[9] = 7更新进去对于这组数据没有什么意义,但是如果后面再出现两个数字 8 和 9,那么就可以把8更新到d[5], 9更新到d[6],得出LIS的长度为6。

然后应该发现一件事情了:在B中插入数据是有序的,而且是进行替换而不需要挪动——也就是说,我们可以使用二分查找,将每一个数字的插入时间优化到O(logN)~~~~~于是算法的时间复杂度就降低到了O(NlogN)~!!


题目:

Description

设有一个序列 ( a 1 , a 2 , . . a n ) (a_{1},a_{2},..a_{n}) a1,a2,..an,如果序列 ( a i 1 , a i 2 , . . . , a i 3 ) (a_{i1}, a_{i2} , …, a_{i3}) (ai1,ai2,...,ai3), 满足 1 &lt; = i 1 &lt; i 2 &lt; . . . &lt; i k &lt; = n 1&lt; = i_{1} &lt; i_{2}&lt; …&lt; i_{k}&lt;=n 1<=i1<i2<...<ik<=n a i 1 &lt; a i 2 &lt; . . . &lt; a i k a_{i1} &lt; a_{i2}&lt; .. .&lt; a_{ik} ai1<ai2<...<aik,则称 ( a i 1 , a i 2 , . . . , a k ) 为 ( a 1 , a 2 , … … a n ) (a_{i1}, a_{i2}, …, a_{k})为(a_{1},a_{2},……a_{n}) (ai1,ai2,...,ak)(a1,a2,an)的一上升子序列。 例如序列1,6,2,5,4,7的一个最长上升子序列是1,2,5,7(还有其他的,这里略去),长度是4.给一个序列,求它的最长上升子序列的长度。

Input

题目有多组测试数据,每组第一行为一个整数n,代表序列的长度,第二行为序列的n个数。(1<=n<=100000)。//数据量比较大经典算法不合适。

Output

每组输出占一行,包含序列的最长上升子序列的长度。

Sample Input

7
1 7 3 5 9 4 8

Sample Output

4

#include<stdio.h>
#include<algorithm>
#include<cstring>
using namespace std;
int main()
{ 
   
    int n,num[100100],lis[100100],len;
    while(scanf("%d",&n)!=EOF)
    { 
   
        len = 0;
        memset(lis,0,sizeof(lis));
	    for(int i=0;i<n;i++)//如果i是从1开始,在lower_bound中的到的位置会返回到0,这样就不可以把lis[1]的位置替换掉,从而WA。
        { 
   
            scanf("%d",&num[i]);
        }
        lis[0] = num[0];
        for(int i=1;i<n;i++)
        { 
   
            if(num[i] > lis[len])//如果num比lis[len]选择的终点大,则可以放入lis,即新的终点。
                lis[++len] = num[i];
            else
            { 
   
                int pos = upper_bound(lis,lis+len,num[i]) - lis;//注意upper_bound 的用法,upper_bound返回的是第一个比它大的数的一个地址
                lis[pos] = num[i];//!!!
            }
        }
        printf("%d\n",len+1);//len是从0开始的,所以要加上1。
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/132005.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 3.4 《数据库系统概论》之数据查询—SELECT(单表查询、连接查询、嵌套查询、集合查询、多表查询)

    3.4 《数据库系统概论》之数据查询—SELECT(单表查询、连接查询、嵌套查询、集合查询、多表查询)文章目录0.前言1.思维导图2.Student/SC/Course表数据及结构3.SELECT语句的一般格式4.单表查询(1)选择表中的若干列①查询指定列②查询全部列③查询经过计算的值❶算术表达式❷字符串常量及函数❸使用列别名改变查询结果的列标题(2)选择表中的若干元组(行)①关键词DISTINCT去掉表中重复的行②查询满足条件的元组(行)❶比较大小❷确定范围❸确定集合❹字…

    2022年6月12日
    25
  • 打印小册子中断了怎么办呢_pdf小册子双面打印

    打印小册子中断了怎么办呢_pdf小册子双面打印在这里可以首先分享下针对小册子的打印方法,像wps针对pdf就提供打印小册子的设置,对于支持双面打印的打印机,小册子子集选择双面即可,而针对只能打单面的打印机,也不要慌,可以分两次打,先选择打正面,在选择打背面即可。这时候问题来了,如果打印的特别多,出现意外中断,比如没墨了,没纸了,很容易打印机无法暂存打印,打印任务就消失了,气的人想吐血。难道真的没有办法了么,…

    2022年9月5日
    12
  • 2.6 从JSON数据源导入数据

    2.6 从JSON数据源导入数据2.6从JSON数据源导入数据1、如何读取json格式的数据在开始之前,需要安装requests模块案例:读取并解析GitHub(http://github.com)网站的最近活动时间表2、操作步骤指定GitHubURL来读取JSON格式数据使用requests模块访问指定的URL,并获取内容读取内容并将之转化为JSON格式的对象迭代访问JSON对象3、代码实现importrequestsimportjsonurl=’https://github.com/ti

    2022年6月19日
    24
  • Python爬虫之女神网图片(三)

    Python爬虫之女神网图片(三)女神网是一个可以搜索女神的图片的网站。废话不说,直接来干货:环境配置:系统环境:WIN7/8/10编译环境:Python3+所需库:requests、re、os、pymongo、Beatifulsoup、timeIDE:Pycharmnvshen.py#!/usr/bin/python#-*-coding:utf-8-*-importrequestsfromrequestsi…

    2022年6月8日
    47
  • [900]mysql字符串数字互转

    [900]mysql字符串数字互转字符串转数字最简单的方式就是直接在字符串后面+0,就相当于直接把字符串转为数字类型了,下面可以看一下具体的操作,可以看到通过+0操作,成功将两个字符串转化为了数字,并得到了相加后的结果。SELECT’123’+0+’123′;CAST()函数转化为整数使用CAST()函数,使用方式为CAST(valueAStype);,下面可以看一下具体的操作例子,通过如下sql语句查看结果:SELECTCAST(‘5.45’ASSIGNED);可以看到结果直接将字符串’5.45’转为了

    2022年5月22日
    33
  • The Class File Viewer cannot handle the given input

    The Class File Viewer cannot handle the given inputThe Class File Viewer cannot handle the given input

    2022年4月24日
    67

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号