博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
最长上升子序列问题
阅读量:5916 次
发布时间:2019-06-19

本文共 2648 字,大约阅读时间需要 8 分钟。

描述一个数的序列bi,当b1 < b2 < ... < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, ..., aN),我们可以得到一些上升的子序列(ai1, ai2, ..., aiK),这里1 <= i1 < i2 < ... < iK <= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8).你的任务,就是对于给定的序列,求出最长上升子序列的长度。输入输入的第一行是序列的长度N (1 <= N <= 1000)。第二行给出序列中的N个整数,这些整数的取值范围都在0到10000。输出最长上升子序列的长度。样例输入71 7 3 5 9 4 8样例输出4来源Northeastern Europe 2002, Far-Eastern Subregion 比赛试题

 


 

首先我们来定义下状态 dp[i]:以ai为末尾的最长上升子序列的长度

        状态转移方程:dp[i] = { max(dp[j]+1) | j<i 且 aj<ai 

用文字解释一下是:

以第i项结尾的LIS的长度是:保证第j项比第i项小的情况下,以第j项结尾的LIS长度加一的最大值,取遍i的所有值(j小于i)

让我们举个例子:求 { 2 7 1 5 6 4 3 8 9 } 的最长上升子序列。为了方便起见,我们定义d(i) (i∈[1,n])来表示前i个数以A[i]结尾的最长上升子序列长度。前1个数 d(1)=1 子序列为2前2个数 7前面有2小于7 d(2)=d(1)+1=2 子序列为2 7前3个数 在1前面没有比1更小的,1自身组成长度为1的子序列 d(3)=1 子序列为1前4个数 5前面有2小于5 d(4)=d(1)+1=2 子序列为2 5 前5个数 6前面有2 5小于6 d(5)=d(4)+1=3 子序列为2 5 6 前6个数 4前面有2小于4 d(6)=d(1)+1=2 子序列为2 4 前7个数 3前面有2小于3 d(3)=d(1)+1=2 子序列为2 3 前8个数 8前面有2 5 6小于8 d(8)=d(5)+1=4 子序列为2 5 6 8 前9个数 9前面有2 5 6 8小于9 d(9)=d(8)+1=5 子序列为2 5 6 8 9 d(i)=max{d(1),d(2),……,d(i)} 我们可以看出这9个数的LIS为d(9)=5

核心代码:

void solve() {    int res = 0;    for (int i = 0; i < n; i++) {        dp[i] = 1;        for (int j = 0; j < i; j++) {            if (a[j] < a[i])                dp [i] = max (dp[i] , dp[j] + 1);        }        res = max(res, dp[i]);    }  printf("%d\n", res);}

 

 

对状态的定义只有一种吗?当然不是

我们可以用完全不同的视角定义这个问题

状态 dp[i]:长度为i+1的上升子序列中末尾元素的最小值(不存在的话就是INF)

状态转移方程:dp[i] = { min(dp[i], aj) |  dp[i-1]<aj 或者 i=0}

我们再举一个例子:有以下序列A[]={ 3 1 2 6 4 5 10 7 },求LIS长度。我们定义一个B[i]来储存可能的排序序列,len为LIS长度。我们依次把A[i]有序地放进B[i]里。(为了方便,这时i的范围就从1~n表示第i个数)A[1]=3,把3放进dp[1],此时dp[1]=3,此时len=1,最小末尾是3A[2]=1,因为1比3小,所以可以把dp[1]中的3替换为1,此时B[1]=1,此时len=1,最小末尾是1A[3]=2,2大于1,就把2放进dp[2]=2,此时B[]={1,2},len=2同理,A[4]=6,把6放进dp[3]=6,B[]={1,2,6},len=3 A[5]=4,4在2和6之间,比6小,可以把dp[3]替换为4,B[]={1,2,4},len=3 A[6]=5,dp[4]=5,dp[]={1,2,4,5},len=4 A[7]=10,dp[5]=10,B[]={1,2,4,5,10},len=5 A[8]=7,7在5和10之间,比10小,可以把dp[5]替换为7,B[]={1,2,4,5,7},len=5

 

核心代码( O(n2) ):

void solve() {    fill(dp, dp+n, INF);    for (int j = 0; j < n; j++) {        for (int i = 0; i < j; i++) {            dp[0] = min(dp[0], a[j]);            if (dp[i]

优化后的代码( O(nlogn) ):

void solve() {    fill(dp, dp+n, INF);//也可以使用memset(dp, 0x3f, sizeof(dp));     for (int i = 0; i < n; i++) {        *lower_bound(dp, dp+n, a[i]) = a[i];        /*整个dp数组除了INF外是呈严格上升的,即dp中插入的数据是有序的,不需要移动,只需要替换,         由此可知对于每个aj最多只需要一次更新,对于更新应该在什么位置,可以利用二分搜索*/        }    printf("%d\n", 1ower_bound(dp, dp+n, INF) - dp);}

 

附:

lower_bound( )和upper_bound( )都是利用二分查找的方法在一个排好序的数组中进行查找的。

lower_bound():返回的是被查序列中第一个大于等于查找值的指针

upper_bound():返回的是被查序列中第一个大于查找值得指针

转载于:https://www.cnblogs.com/wizarderror/p/10462496.html

你可能感兴趣的文章
运算符 - PHP手册笔记
查看>>
二维数组的认识及其表示元素的两种方式
查看>>
LINUX下DNS的查看和配置
查看>>
分布式事务系列(1.2)Spring的事务体系
查看>>
docker search, pull, login, push with Docker Hub - public registry
查看>>
正则表达式学习
查看>>
微信红包接口集成流程
查看>>
FL2440字符设备驱动之LED
查看>>
使用Webpack + Gulp便捷开发运行于Dcloud平台HTML5 Plus Runtime引擎的HybridAPP项目的一些经验分享...
查看>>
前端进阶之 a 可以同时 == 1 && == 2 && == 3吗?
查看>>
告别抽象的JS执行环境、作用域、作用域链、闭包
查看>>
9012到了!AI时代里,只会简单编程的你会怎么样?
查看>>
1月10日云栖精选夜读:专访金榕:四年蜕变,阿里iDST是如何登上浪潮之巅的?...
查看>>
区块链100讲:梅克尔树保障区块链数据不可篡改,想换根就要砍树!
查看>>
如何使用jstack分析线程状态
查看>>
运营不需要人脉?
查看>>
全方位解读Java反射(reflection)
查看>>
Spring Cloud Config服务器
查看>>
fprobe使用
查看>>
yum 安装rabbitMQ
查看>>