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. Header解析

    不管是作为后端还是前端开发人员,对于web请求的过程和参数都是需要了解的. 下面是对一次简单的http请求的header分析,作为自己的一个总结,也希望对大家有所帮助. 以Chrome为例: 我们对h ...

  2. Can you find it? HDU-2141 (二分查找模版题)

    Description Give you three sequences of numbers A, B, C, then we give you a number X. Now you need t ...

  3. 苹果审核不通过,程序/游戏不兼容IPV6网络

    最近苹果升级的IOS10,所以那边网络环境变成IPV6,如果你的程序不兼容IPV6,苹果的程序会以这个不兼容的原因驳回审核. 那么如何让自己的程序兼容这个?方法其实C#本来已经提供给你的,而且很简单, ...

  4. php array_key_exists() 与 isset() 的区别

    一个基本的区别是isset()可用于数组和变量,而array_key_exits()只能用于数组. 但是最主要的区别在于在设定的条件下的返回值. 现在我们来验证一下这个最主要的区别. array_ke ...

  5. node.js的path模块

    path模块的各种API path.join([...paths]) 参数:paths <string> ,paths参数是字符串,这些字符串按路径片段顺序排列,(A sequence o ...

  6. RapidMiner Studio 入门

    http://docs.rapidminer.com/studio/getting-started/ RapidMiner Studio 入门 FEIFEI MA 2015-12-07RAPIDMIN ...

  7. LeetCode169 Majority Element, LintCode47 Majority Number II, LeetCode229 Majority Element II, LintCode48 Majority Number III

    LeetCode169. Majority Element Given an array of size n, find the majority element. The majority elem ...

  8. Python接口自动化(一)接口基础

    HTTP接口熟悉 常见接口介绍 接口工具的使用 fiddler如何mock数据 常见接口基础面试 如何理解接口?前后端解耦,前端和后端数据对接桥梁 接口测试和功能测试区别在哪?接口测试是功能测试的一种 ...

  9. 基于Spark Mllib的Spark NLP库

    SparkNLP的官方文档 1>sbt引入: scala为2.11时 libraryDependencies += "com.johnsnowlabs.nlp" %% &qu ...

  10. 开发一个登录接口(Mysql)

    分享一段代码,开发了一个登录接口: 使用Python开发,需要安装flask模块,使用pip intall flask 安装即可,这里使用的数据库是Mysql,所以导入了pymysql模块,代码如下: ...