p275

d(i)是以Ai为结尾的最长上升子序列的长度

《算法竞赛入门经典-训练指南》p62 问题6 提供了一种优化到 O(nlogn)的方法。

文本中用g(i)表示d值为i的最小状态编号(数组下标),满足

g(1) <= g(2) <= g(3) <= ... <= g(n)

可以用反证法:

假设 i < j, g(i) > g(j)

g(j)代表 d(x) = j 的最小 x,对应的LIS的长度为j,最后一个元素是Ax,设这个LIS为LISj

g(i)代表 d(y) = i 的最小 y,对应的LIS的长度为i,最后一个元素是Ay

LISj有j个成员,其中取i个成员(不包括最后一个元素)也是一个LIS,长度为i,这个LIS的最后一个成员的下标 < g(j) < g(i),而 g(i) 应该是长度为i的LIS的最小下标,所以矛盾,得证 #

代码中实际上是用g(i)表示d值为i的多个LIS中,最后一个元素最小的LIS的最后一个元素的值,也满足

g(1) <= g(2) <= g(3) <= ... <= g(n)

可以用反证法:

假设 i < j, g(i) > g(j)

g(j)代表 d(x) = j 的最小 x,对应的LIS的长度为j,最后一个元素是x,设这个LIS为LISj

g(i)代表 d(y) = i 的最小 y,对应的LIS的长度为i,最后一个元素是y

LISj有j个成员,其中取i个成员(不包括最后一个元素)也是一个LIS,长度为i,这个LIS的最后一个成员的值 = z < g(j) = x < g(i) = y,而 g(i) 应该是长度为i的LIS的末尾元素最小值,所以矛盾,得证 #

    for (int i = ; i <= n; i++) g[i] = INF;

    for (int i = ; i < n; i++) {
// k是满足 g[k] >= A[i] 的最小下标
// k' = k - 1 是满足 g[k] < A[i] 的最大(最后一个)下标,A[i] 可以放在g[k-1]对应的长度为k-1的LIS的后面,形成一个新的长度为k的LIS
// 这个新的LIS的长度为k,最后一个元素是A[i], 所以设置 d[i] = k;
// 此时g[k] >= A[i], 所以设置 g[k] = A[i];
int k = lower_bound(g + , g + n + , A[i]) - g; // 在g[1]到g[n]中找
d[i] = k;
g[k] = A[i];
}

数组g[]是排序的

最后的答案是 lower_bound(g + 1, g + 1+ n, INF),也就是数组g[]中不是INF的最大下标

《挑战程序设计竞赛》p64 也有类似分析

 网上有个例子

假设存在一个序列A[1..9] = 2 1 5 3 6 4 8 9 7,可以看出来它的LIS长度为5。n 下面一步一步试着找出它。 我们定义一个序列g,然后令 i = 1 to 9 逐个考察这个序列。 此外,我们用一个变量Len来记录现在最长算到多少了

首先,把A[1]有序地放到g里,令g[1] = 2,就是说当只有1个数字2的时候,长度为1的LIS的最小末尾是2。这时Len=1

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

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

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

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

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

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

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

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

于是我们知道了LIS的长度为5。

!!!!! 注意。这个1,3,4,7,9不是LIS,它只是存储的对应长度LIS的最小末尾。有了这个末尾,我们就可以一个一个地插入数据。

虽然最后一个A[9] = 7更新进去对于这组数据没有什么意义,但是如果后面再出现两个数字 8 和 9,那么就可以把8更新到A[5], 9更新到A[6],得出LIS的长度为6。

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

另一个网上的说明

对于序列Sn,考虑其长度为i的单调子列(1<=i<=m),这样的子列可能有多个。我们选取这些子列的结尾元素(子列的最后一个元素)的最小值。用Li表示。易知

L1<=L2<=…<=Lm

如果Li>Lj(i<j),那么去掉以Lj结尾的递增子序列的最后j-i个元素,得到一个长度为i的子序列,该序列的结尾元素ak<=Lj<Li,这与Li标识了长度为i的递增子序列的最小结尾元素相矛盾,于是证明了上述结论。

现在,我们来寻找Sn对应的L序列,如果我们找到的最大的Li是Lm,那么m就是最大单调子列的长度。下面的方法可以用来维护L。

从左至右扫描Sn,对于每一个ai,它可能

(1)    ai<L1,那么L1=ai

(2)    ai>=Lm,那么Lm+1=ai,m=m+1 (其中m是当前见到的最大的L下标)

(3)    Ls<=ai<Ls+1,那么Ls+1=ai

 

扫描完成后,我们也就得到了最长递增子序列的长度。从上述方法可知,对于每一个元素,我们需要对L进行查找操作,由于L有序,所以这个操作为logn,于是总的复杂度为O(nlogn)。

lrj 9.4.1 最长上升子序列 LIS的更多相关文章

  1. 2.16 最长递增子序列 LIS

    [本文链接] http://www.cnblogs.com/hellogiser/p/dp-of-LIS.html [分析] 思路一:设序列为A,对序列进行排序后得到B,那么A的最长递增子序列LIS就 ...

  2. 最长上升子序列LIS(51nod1134)

    1134 最长递增子序列 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题 收藏 关注 给出长度为N的数组,找出这个数组的最长递增子序列.(递增子序列是指,子序列的元素是递 ...

  3. 动态规划(DP),最长递增子序列(LIS)

    题目链接:http://poj.org/problem?id=2533 解题报告: 状态转移方程: dp[i]表示以a[i]为结尾的LIS长度 状态转移方程: dp[0]=1; dp[i]=max(d ...

  4. 【部分转载】:【lower_bound、upperbound讲解、二分查找、最长上升子序列(LIS)、最长下降子序列模版】

    二分 lower_bound lower_bound()在一个区间内进行二分查找,返回第一个大于等于目标值的位置(地址) upper_bound upper_bound()与lower_bound() ...

  5. 题解 最长上升子序列 LIS

    最长上升子序列 LIS Description 给出一个 1 ∼ n (n ≤ 10^5) 的排列 P 求其最长上升子序列长度 Input 第一行一个正整数n,表示序列中整数个数: 第二行是空格隔开的 ...

  6. 最长回文子序列LCS,最长递增子序列LIS及相互联系

    最长公共子序列LCS Lintcode 77. 最长公共子序列 LCS问题是求两个字符串的最长公共子序列 \[ dp[i][j] = \left\{\begin{matrix} & max(d ...

  7. 一个数组求其最长递增子序列(LIS)

    一个数组求其最长递增子序列(LIS) 例如数组{3, 1, 4, 2, 3, 9, 4, 6}的LIS是{1, 2, 3, 4, 6},长度为5,假设数组长度为N,求数组的LIS的长度, 需要一个额外 ...

  8. 1. 线性DP 300. 最长上升子序列 (LIS)

    最经典单串: 300. 最长上升子序列 (LIS) https://leetcode-cn.com/problems/longest-increasing-subsequence/submission ...

  9. 动态规划 - 最长递增子序列(LIS)

    最长递增子序列是动态规划中经典的问题,详细如下: 在一个已知的序列{a1,a2,...,an}中,取出若干数组组成新的序列{ai1,ai2,...,aim},其中下标i1,i2,...,im保持递增, ...

随机推荐

  1. 微信小程序--轮播图,标题,盒子,tab栏的合成例子

    小程序是什么? 微信小程序,是一种不需要下载安装即可使用的应用,用户扫一扫或搜一下即可打开应用,在微信-发现-小程序可打开应用. 一.小程序的样式编写: 目录结构: app.json { " ...

  2. python中数字转换成字符串

    数字转换成字符串: num=123 str='%d' %num str就变成了"123"

  3. Android消息机制使用注意事项,防止泄漏

    在Android的线程通信当中,使用频率最多的就是Android的消息处理机制(Handler.send().View.post().Asynctask.excute()等等都使用到了消息处理机制). ...

  4. Redhad的开源Paas平台:OpenShift

    参考redHat的官方文章翻译而来:https://openshift.redhat.com/community/wiki/architecture-overview OpenShift Origin ...

  5. JavaScript实现,控制一个文本框只能输入正整数,如输入不符合条件则文本框全部字体标红

    腾讯2014年研发职位笔试题Web前端方向简单题第一题. 代码: <html> <head> <title>test JavaScript</title> ...

  6. HTML5入门指南

    1.HTML5到底是什么? HTML5是HTML最新的修订版本,2014年10月由万维网联盟(W3C)完成标准制定.目标是取代1999年所制定的HTML 4.01和XHTML 1.0标准,以期能在互联 ...

  7. 从零学React Native之10Text

    在React Native开发中,所有需要显示的字符串文本都需要放置在Text或者Text的子组件中.虽然在之前的文章中多次使用了Text组件,但是Text组件还是值得专门学习的, 并没有想象中的那么 ...

  8. spoj SUBLEX (Lexicographical Substring Search) RE的欢迎来看看

    SPOJ.com - Problem SUBLEX 这么裸的一个SAM,放在了死破OJ上面就是个坑. 注意用SAM做的时候输出要用一个数组存下来,然后再puts,不然一个一个字符输出会更慢. 还有一个 ...

  9. 中断源记录 INT0 INT1

    中断源记录 INT0 INT1 用到一个单片机 使用的 P3.1 P3.3 作为唤醒口,后来发一 P3.1 和 P3.3 使用的同一个中断 INT1,这个尴尬了,只能两选 一. 查看规格书,还好 P3 ...

  10. 2019-10-22-Roslyn-打包自定义的文件到-NuGet-包

    title author date CreateTime categories Roslyn 打包自定义的文件到 NuGet 包 lindexi 2019-10-22 19:45:34 +0800 2 ...