最长递增子序列(LIS)
2015-10-09 11:44
295 查看
文章作者:Yx.Ac 文章来源:勇幸|Thinking (http://www.ahathinking.com) 转载请注明,谢谢合作。
---
最长递增子序列又叫做最长上升子序列;子序列,正如LCS一样,元素不一定要求连续。本节讨论实现三种常见方法,主要是练手。
题:求一个一维数组arr[i]中的最长递增子序列的长度,如在序列1,-1,2,-3,4,-5,6,-7中,最长递增子序列长度为4,可以是1,2,4,6,也可以是-1,2,4,6。
方法一:DP
像LCS一样,从后向前分析,很容易想到,第i个元素之前的最长递增子序列的长度要么是1(单独成一个序列),要么就是第i-1个元素之前的最长递增子序列加1,可以有状态方程:
LIS[i] = max{1,LIS[k]+1},其中,对于任意的k<=i-1,arr[i] > arr[k],这样arr[i]才能在arr[k]的基础上构成一个新的递增子序列。
代码如下:在计算好LIS长度之后,output函数递归输出其中的一个最长递增子序列。
这个方法也最容易想到也是最传统的解决方案,对于该方法和LIS,有以下两点说明:
由LIS可以衍生出来最长非递减子序列,最长递减子序列,道理是一样的。
对于输出序列,也是可以再申请一数组pre[i]记录子序列中array[i]的前驱,道理跟本节的实现也是一样的
方法二:排序+LCS
这个方法是在Felix’blog(见参考资料)中看到的,因为简单,他在博文中只是提了一句,不过为了练手,虽然懒,还是硬着头皮写一遍吧,正好再写一遍快排,用quicksort + LCS,这个思路还是很巧妙的,因为LIS是单调递增的性质,所以任意一个LIS一定跟排序后的序列有LCS,并且就是LIS本身。代码如下:
方法三:DP+二分查找
《编程之美》对于这个方法有提到,不过它的讲解我看得比较难受,好长时间才明白,涉及到的数组也比较多,除了源数据数组,有LIS[i]和MaxV[LIS[i]],后来看了大牛Felix的讲解,我才忽然发现编程之美中的这个数组MaxV[LIS[i]]在记录信息上其实是饶了弯的,因为我们在寻找某一长度子序列所对应的最大元素最小值时,完全没必要通过LIS[i]去定位,即没必要与数据arr[i]挂钩,直接将MaxV[i]的下标作为LIS的长度,来记录最小值就可以了(表达能力太次,囧。。。),一句话,就是不需要LIS[i]这个数组了,只用MaxV[i]即可达到效果,而且原理容易理解,代码表达也比较直观、简单。
下面说说原理:
目的:我们期望在前i个元素中的所有长度为len的递增子序列中找到这样一个序列,它的最大元素比arr[i+1]小,而且长度要尽量的长,如此,我们只需记录len长度的递增子序列中最大元素的最小值就能使得将来的递增子序列尽量地长。
方法:维护一个数组MaxV[i],记录长度为i的递增子序列中最大元素的最小值,并对于数组中的每个元素考察其是哪个子序列的最大元素,二分更新MaxV数组,最终i的值便是最长递增子序列的长度。这个方法真是太巧妙了,妙不可言。
代码如下:
这个方法的实现巧妙而直观,让人有种“啊,原来还可以这样”的感慨,感谢Felix。
本文相关代码可以到这里下载。
(全文完)
参考资料:
《编程之美》 2.16
Felix’s Blog:最长递增子序列 O(NlogN)算法
勘误:
第三种方法细节有错误,见下面评论,感谢@冰上游鱼
---
最长递增子序列又叫做最长上升子序列;子序列,正如LCS一样,元素不一定要求连续。本节讨论实现三种常见方法,主要是练手。
题:求一个一维数组arr[i]中的最长递增子序列的长度,如在序列1,-1,2,-3,4,-5,6,-7中,最长递增子序列长度为4,可以是1,2,4,6,也可以是-1,2,4,6。
方法一:DP
像LCS一样,从后向前分析,很容易想到,第i个元素之前的最长递增子序列的长度要么是1(单独成一个序列),要么就是第i-1个元素之前的最长递增子序列加1,可以有状态方程:
LIS[i] = max{1,LIS[k]+1},其中,对于任意的k<=i-1,arr[i] > arr[k],这样arr[i]才能在arr[k]的基础上构成一个新的递增子序列。
代码如下:在计算好LIS长度之后,output函数递归输出其中的一个最长递增子序列。
由LIS可以衍生出来最长非递减子序列,最长递减子序列,道理是一样的。
对于输出序列,也是可以再申请一数组pre[i]记录子序列中array[i]的前驱,道理跟本节的实现也是一样的
方法二:排序+LCS
这个方法是在Felix’blog(见参考资料)中看到的,因为简单,他在博文中只是提了一句,不过为了练手,虽然懒,还是硬着头皮写一遍吧,正好再写一遍快排,用quicksort + LCS,这个思路还是很巧妙的,因为LIS是单调递增的性质,所以任意一个LIS一定跟排序后的序列有LCS,并且就是LIS本身。代码如下:
《编程之美》对于这个方法有提到,不过它的讲解我看得比较难受,好长时间才明白,涉及到的数组也比较多,除了源数据数组,有LIS[i]和MaxV[LIS[i]],后来看了大牛Felix的讲解,我才忽然发现编程之美中的这个数组MaxV[LIS[i]]在记录信息上其实是饶了弯的,因为我们在寻找某一长度子序列所对应的最大元素最小值时,完全没必要通过LIS[i]去定位,即没必要与数据arr[i]挂钩,直接将MaxV[i]的下标作为LIS的长度,来记录最小值就可以了(表达能力太次,囧。。。),一句话,就是不需要LIS[i]这个数组了,只用MaxV[i]即可达到效果,而且原理容易理解,代码表达也比较直观、简单。
下面说说原理:
目的:我们期望在前i个元素中的所有长度为len的递增子序列中找到这样一个序列,它的最大元素比arr[i+1]小,而且长度要尽量的长,如此,我们只需记录len长度的递增子序列中最大元素的最小值就能使得将来的递增子序列尽量地长。
方法:维护一个数组MaxV[i],记录长度为i的递增子序列中最大元素的最小值,并对于数组中的每个元素考察其是哪个子序列的最大元素,二分更新MaxV数组,最终i的值便是最长递增子序列的长度。这个方法真是太巧妙了,妙不可言。
代码如下:
本文相关代码可以到这里下载。
(全文完)
参考资料:
《编程之美》 2.16
Felix’s Blog:最长递增子序列 O(NlogN)算法
勘误:
第三种方法细节有错误,见下面评论,感谢@冰上游鱼
相关文章推荐
- [LeetCode 290] Word Pattern
- Xcode模拟器不能选择 强制打开提示iOS Simulator 意外退出
- Java 集合系列16之 HashSet详细介绍(源码解析)和使用示例
- Java 集合系列15之 Set架构
- c位运算
- Undefined symbols for architecture x86_64: "_crc32", 问题的解决
- 循环队列_queue
- 006-storm集群搭建
- java例子6:抽象类,形状
- shell特殊变量 字符截取
- java中List、Map、Set、Collection、Stack、Queue等的使用
- 多数据库(表)同步
- 从jar文件中读取指定文件,并以File对象返回?
- auto_ptr 使用详解
- 开源简单的富文本编辑器大集合
- 采用oauth2.0授权导致更换账号时使用原账号自动登录
- Mybatis删增没问题,查询出来结果为空
- 广播退出多个Activity
- hibernate fetch使用
- haproxy实现动静分离机制