关于LIS(Least Increasing Subsequence,可译为最长递增子序列、最长上升子序列),其O(n2)和O(nlgn)的算法可以见博文[1][2]。

O(n2)的算法很早之前就听说,不算新鲜,但O(nlgn)的算法还是第一次听说,觉得很新鲜,也很牛。既然很多人都引用了这个算法我相信这个算法一定是正确的,但是引用这些算法的博文却并没有给出这个算法的一个特别准确的描述,也没有对其正确性进行证明。题目的出处(参见[3]的扩展1)对这个算法的解释较笼统,无法确知为何通过二分查找能得到正确的结果,反而是复杂度较为容易理解。通过[1][2]的叙述,自己得到了一些本算法为何正确的灵感,因此对这个算法试图更精确描述如下。

首先,引用[1]对算法的叙述方式:



另外还有一种O(n log k)的算法,其中k是实际LIS的长度。算法使用一个升序排列的序列A来存储整个LIS。A的初始状态为1个负无穷大和n-1个正无穷大组成的数组。我们要做的就是对整个序列来一次遍历,然后把每个数放到A中去看它是否是属于这个LIS,如果是就把它插入其中。这里所谓是否属于就是指从A的第一个非无穷大的数往前看,如果找到这个一个位置,即前面的数小于这个待插入的数,且后一个数大于那个数。插入的方法就是把后面的那个数替代掉即可。这种查找使用的是Binary Search,它时间复杂度为O(log k)。所以最终整个算法的时间复杂度为O(n log k)。下面给出一个例子:


   0  1  2  3  4  5  6  7  8
a    -7,10, 9, 2, 3, 8, 8, 1

A -i  i, i, i, i, i, i, i, i
A -i -7, i, i, i, i, i, i, i (1)
A -i -7,10, i, i, i, i, i, i (2)
A -i -7, 9, i, i, i, i, i, i (3)
A -i -7, 2, i, i, i, i, i, i (4)
A -i -7, 2, 3, i, i, i, i, i (5)
A -i -7, 2, 3, 8, i, i, i, i (6)
A -i -7, 2, 3, 8, i, i, i, i (7)
A -i -7, 1, 3, 8, i, i, i, i (8)



在这个算法中,关键的问题是数组A如何理解。在这里设定O(n2)的算法为算法1,O(nlgn)的算法为算法2。为了要证明算法2的正确性,如果能证明算法2的内层循环能完成和算法1的一样的功能,那么即可得证。于是首先要知道的是算法1的内层循环到底完成了一个什么样的功能。用一句话描述如下:

对于当前第i个元素,找到结尾元素比a[i]小且d[k](数组d为以第k元素为结尾的最长递增子序列,k为1~i之间任意数字)最大的那个的元素。

那么算法2中A数组保存了什么呢?用一句话描述可以为:

对于当前元素a[i],元素A[k]是在考虑了前i-1个元素后,长度为k的子序列结尾最小的元素。

其实可知,算法2与算法1中的内存循环完成的功能是一样。因为A中保存的是长度为k的子序列结尾最小元素,而算法2的内层循环实际上是在寻找A数组中小于当前元素的最大元素(暂且不考虑二分搜索,而假设使用普通的线性扫描,后面会证明A数组一定是有序的)。这个过程可以理解实际上是在找结尾小于当前元素的最大的k。这与算法1是相同的。

接下来证明A数组一定是递增的。用反证法,假设对某个k,A[k+1]<A[k]。如果A[k+1]那个元素在原数组中在A[k]之后,那么在以A[k+1]结尾的k+1长度的子序列中,一定有一个子序列长度为k而结尾元素小于A[k](即为A[k+1]的子序列抛去A[k+1],新序列的结尾元素<A[k+1]<A[k]),这与A[k]为k长度的子序列的最小结尾这个条件矛盾。加啥A[k+1]那个元素在原数组中在A[k]之前,那么A[k+1]的序列加上A[k]可以组成一个k+2的序列,这也与A[k]的性质矛盾。所以对所有k,A[k+1]>A[k],故数组A为递增。

对于递增的数组A,查找可以使用二分,故算法的复杂度为O(nlgn)。

 

参考博客:


[2] http://www.slyar.com/blog/longest-ordered-subsequence.html