一、本文内容
最长递增子序列的两种动态规划算法实现,O(n^2)及O(nlogn).
 
 
二、问题描述
最长递增子序列:给定一个序列,从该序列找出最长的 升序/递增 子序列。
特点:1、子序列不要求连续; 2、子序列在原序列中按严格(strictly)升序排序; 3、最长递增子序列不唯一。
 
注:下文最长递增子序列用缩写LIS表示。
 
example:
0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15
 
对应的LIS:
0, 2, 6, 9, 13, 15
0, 4, 6, 9, 11, 15
 
 
三、算法描述
1、考察第i+1个元素时,不考虑前面i个元素的状态
给定长度为n的序列A[0..n-1],对于给定某个的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能间接通过当前状态来影响;换句话说,每个状态都是过去历史的完整总结。即LIS[i]当前的状态与LIS[0..i-1]已无关。这几句话可以用Fibonacci的递归树来验证,对LIS的递归树同样适用。 如下面的递归树:
 
                                    LIS(3)          
                                /       |      \
                      LIS(2)     LIS(1)    LIS(0) 
                      /     \       /        
             LIS(1)  LIS(0)  LIS(0)
            /   
         LIS(0) 
 
很显然,递归树有很多重复/重叠的子问题,而大问题的最优解又可以由这些子问题的最优解得到,符合DP的两个条件,故可以用DP来解决LIS问题。
那么给定序列A[0..n-1],求它的LIS,可以top-down分解任务,得到递归树;递归树bottom-up就可以建立子问题的查询表以供求解当前问题时查询。
这就是DP的两种处理方式:Memoization(top-down) 和 Tabulation (bottom-up).
 
假设LIS[i]表示A[0..i]的最长递增子序列的长度,那么
LIS[i+1] = max{1, LIS[j]+1}, A[i+1]>A[j], for any j<=i;   注意:到A[i]的子序列长度不一定大于A[j]的子序列长度(如上例当A[j]=12, LIS[j]={0, 4, 12}和当A[i]=2, LIS[i]={0, 2})
若A[i+1]>A[j],则A[i+1]可以接在LIS[j]长的子序列后面构成一个更长的子序列。
同时, 从A[i+1]开始又构成一个长度为1的子序列。故两者取大值。
 
 
2、考察第i+1个元素时,考虑前面i个元素的状态
那么,什么时候在一个已存在的序列中添加或者替换一个元素是安全的呢?(DP算法都是offline algorithm,需要全局考虑)
显然,我们需要维护所有递增的子序列(称其为active list,即可能成为max{LIS}的子序列)。而这些子序列的长度都不同,
可以按照插入排序的思想,一个一个元素地从前面找到其应该归属的子序列(active list)。
 
有A[i]>A[i-1]和A[i]<A[i-1]两种情况,A[i]<A[i-1]又有一种特殊情况A[i]是当前序列中的最小元素,即A[i]<A[j], for any j<i;
 
case 1. If A[i] is smallest among all end candidates of active lists, we will start new active list of length 1. 
(when we encounter new smallest element in the array, it can be a potential candidate to start new sequence,{2, 5, 3, 1, 2, 3, 4, 5, 6})
 
case 2. If A[i] is largest among all end candidates of active lists, we will clone the largest active list, and extend it by A[i].
 
case 3. If A[i] is in between, we will find a list with largest end element that is smaller than A[i]. Clone and extend this list by A[i]. We will discard all other lists of same length as that of this modified list. 
(找到end element小于A[i]的active list之后,其他相同长度的active list将被删除,因为A[i]小于这些等长active list的end element,用新的active list代替将被删除的active list:复制小于A[i]的active list,并添加A[i])
 
处理过程如下
example:A[ ] = {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15}
A[0] = 0. Case 1. There are no active lists, create one.
0.
-----------------------------------------------------------------------------
A[1] = 8. Case 2. Clone and extend.
0.
0, 8.
-----------------------------------------------------------------------------
A[2] = 4. Case 3. Clone, extend and discard.
0.
0, 4.
0, 8. Discarded
-----------------------------------------------------------------------------
A[3] = 12. Case 2. Clone and extend.
0.
0, 4.
0, 4, 12.
-----------------------------------------------------------------------------
A[4] = 2. Case 3. Clone, extend and discard.
0.
0, 2.
0, 4. Discarded.
0, 4, 12.
-----------------------------------------------------------------------------
A[5] = 10. Case 3. Clone, extend and discard.
0.
0, 2.
0, 2, 10.
0, 4, 12. Discarded.
-----------------------------------------------------------------------------
A[6] = 6. Case 3. Clone, extend and discard.
0.
0, 2.
0, 2, 6.
0, 2, 10. Discarded.
-----------------------------------------------------------------------------
A[7] = 14. Case 2. Clone and extend.
0.
0, 2.
0, 2, 6.
0, 2, 6, 14.
-----------------------------------------------------------------------------
A[8] = 1. Case 3. Clone, extend and discard.
0.
0, 1.
0, 2. Discarded.
0, 2, 6.
0, 2, 6, 14.
-----------------------------------------------------------------------------
A[9] = 9. Case 3. Clone, extend and discard.
0.
0, 1.
0, 2, 6.
0, 2, 6, 9.
0, 2, 6, 14. Discarded.
-----------------------------------------------------------------------------
A[10] = 5. Case 3. Clone, extend and discard.
0.
0, 1.
0, 1, 5.
0, 2, 6. Discarded.
0, 2, 6, 9.
-----------------------------------------------------------------------------
A[11] = 13. Case 2. Clone and extend.
0.
0, 1.
0, 1, 5.
0, 2, 6, 9.
0, 2, 6, 9, 13.
-----------------------------------------------------------------------------
A[12] = 3. Case 3. Clone, extend and discard.
0.
0, 1.
0, 1, 3.
0, 1, 5. Discarded.
0, 2, 6, 9.
0, 2, 6, 9, 13.
-----------------------------------------------------------------------------
A[13] = 11. Case 3. Clone, extend and discard.
0.
0, 1.
0, 1, 3.
0, 2, 6, 9.
0, 2, 6, 9, 11.
0, 2, 6, 9, 13. Discarded.
-----------------------------------------------------------------------------
A[14] = 7. Case 3. Clone, extend and discard.
0.
0, 1.
0, 1, 3.
0, 1, 3, 7.
0, 2, 6, 9. Discarded.
0, 2, 6, 9, 11.
----------------------------------------------------------------------------
A[15] = 15. Case 2. Clone and extend.
0.
0, 1.
0, 1, 3.
0, 1, 3, 7.
0, 2, 6, 9, 11.
0, 2, 6, 9, 11, 15. <-- LIS List
----------------------------------------------------------------------------
 
注:观察上面的处理过程,我们都只在处理所有active list的最后一个元素end element(粗体),那么仅仅需要维护所有active list构成的end element集合(粗体斜边),
      可以用一维数组来存储。discard操作可以用replace操作来模拟。
 
 
四、算法实现
 
第三节的算法描述序号分别对应下面的算法实现
1、时间复杂度为O(n^2)的DP实现
 /**
@description: Longest Increasing Subsequence
@author: seiyagoo
@create: 2013.10.25
@modified: 2013.10.26
**/
int LIS_1(int A[], int size){ int *LIS = new int[size];
vector<int> *vec = new vector<int>[size]; /* Compute optimized LIS values in bottom up manner */
for(int i=; i < size; i++){
LIS[i]=; //初始化默认长度
int max_j=, flag=;
for(int j=; j < i; j++){ //查表,找出前面最长的序列, 若将A[i]加入LIS[j](LIS[j]+1的含义)的递增子序列比当前的LIS[i]更长, 则更新LIS[i]
if(A[i] > A[j] && LIS[i] < LIS[j]+){
LIS[i] = LIS[j]+;
max_j=j;
flag=;
}
}
if(flag) //copy前面最长子序列到vec[i]
vec[i].insert(vec[i].end(), vec[max_j].begin(), vec[max_j].end());
vec[i].push_back(A[i]); //最后放入A[i]
} /*Show LIS of the current state*/
vector<int>::iterator it;
cout<<left;
for(int i=; i<size; i++){
cout<<setw()<<A[i]<< " --> ";
for(it = vec[i].begin(); it!=vec[i].end(); it++)
cout<<*it<<" ";
cout<<endl;
} /* Pick maximum of all LIS values, namely max{LIS[i]} */
int max_len=;
for(int i = ; i < size; i++ )
if( max_len < LIS[i] )
max_len = LIS[i]; delete[] LIS;
delete[] vec; return max_len;
}
2、时间复杂度为O(nlogn)的DP实现
 /**
@description: Longest Increasing Subsequence
@author: seiyagoo
@create: 2013.10.25
@modified: 2013.10.26
**/ // Binary search (note boundaries in the caller)
// A[] is ceilIndex in the caller
int CeilIndex(int A[], int l, int r, int key) {
int m; while( r - l > ) {
m = l + (r - l)/;
(A[m] >= key ? r : l) = m; // ternary expression returns an l-value
} return r;
} int LIS_2(int A[], int size) {
// boundary case: when array size is one
if( == size ) return ; int *tailTable = new int[size];
vector<int> *vec = new vector<int>[size];
int len; // always points empty slot //memset(tailTable, INT_MAX, sizeof(tailTable[0])*size); @bug for(int i = ; i < size; i++)
tailTable[i] = INT_MAX; tailTable[] = A[]; //tailTable[0] store the smallest value
vec[].push_back(A[]); len = ;
for( int i = ; i < size; i++ ) {
if( A[i] < tailTable[] ) { //case 1: new smallest value
tailTable[] = A[i]; /*discard and create*/
vec[].clear();
vec[].push_back(A[i]);
}
else if( A[i] > tailTable[len-] ) { //case 2: A[i] wants to extend largest subsequence
tailTable[len++] = A[i]; /*clone and extend*/
vec[len-] = vec[len-];
vec[len-].push_back(A[i]);
}
else { //case 3: A[i] wants to be current end candidate of an existing subsequence, It will replace ceil value in tailTable
int ceilIndex = CeilIndex(tailTable, -, len-, A[i]);
tailTable[ceilIndex] = A[i]; /*discard, clone and extend*/
vec[ceilIndex].clear();
vec[ceilIndex] = vec[ceilIndex-];
vec[ceilIndex].push_back(A[i]);
} /*Printf all the active lists*/
vector<int>::iterator it;
cout<<left;
cout<<"A["<<i<<"] = "<<A[i]<<endl<<endl;
cout<<"active lists:"<<endl;
for(int i=; i<len; i++){
for(it = vec[i].begin(); it!=vec[i].end(); it++)
cout<<*it<<" ";
cout<<endl;
} /*Printf end elements of all the active lists*/
cout<<endl<<"end elements array:"<<endl;
for(int i = ; i < size; i++)
if(tailTable[i] != INT_MAX)
cout<<tailTable[i]<<" ";
cout<<endl;
cout<<"-------------------------"<<endl;
} delete[] tailTable;
delete[] vec; return len;
}

五、运行结果

example:

0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15
 
算法实现一
 
算法实现二
 
 
参考:
《编程之美》

Longest Increasing Subsequences(最长递增子序列)的两种DP实现的更多相关文章

  1. leetcode300. Longest Increasing Subsequence 最长递增子序列 、674. Longest Continuous Increasing Subsequence

    Longest Increasing Subsequence 最长递增子序列 子序列不是数组中连续的数. dp表达的意思是以i结尾的最长子序列,而不是前i个数字的最长子序列. 初始化是dp所有的都为1 ...

  2. [LintCode] Longest Increasing Subsequence 最长递增子序列

    Given a sequence of integers, find the longest increasing subsequence (LIS). You code should return ...

  3. [LeetCode] Longest Increasing Subsequence 最长递增子序列

    Given an unsorted array of integers, find the length of longest increasing subsequence. For example, ...

  4. 673. Number of Longest Increasing Subsequence最长递增子序列的数量

    [抄题]: Given an unsorted array of integers, find the number of longest increasing subsequence. Exampl ...

  5. [LeetCode] 300. Longest Increasing Subsequence 最长递增子序列

    Given an unsorted array of integers, find the length of longest increasing subsequence. Example: Inp ...

  6. [leetcode]300. Longest Increasing Subsequence最长递增子序列

    Given an unsorted array of integers, find the length of longest increasing subsequence. Example: Inp ...

  7. poj 2533 Longest Ordered Subsequence 最长递增子序列

    作者:jostree 转载请注明出处 http://www.cnblogs.com/jostree/p/4098562.html 题目链接:poj 2533 Longest Ordered Subse ...

  8. [LeetCode] Number of Longest Increasing Subsequence 最长递增序列的个数

    Given an unsorted array of integers, find the number of longest increasing subsequence. Example 1: I ...

  9. POJ 2533 - Longest Ordered Subsequence - [最长递增子序列长度][LIS问题]

    题目链接:http://poj.org/problem?id=2533 Time Limit: 2000MS Memory Limit: 65536K Description A numeric se ...

  10. [LeetCode] 673. Number of Longest Increasing Subsequence 最长递增序列的个数

    Given an unsorted array of integers, find the number of longest increasing subsequence. Example 1: I ...

随机推荐

  1. Ansible学习记录二:命令

    0.ansible 命令参数详解: [root@localhost ~]# ansible Usage: ansible <host-pattern> [options] Options: ...

  2. python hmac 加密

    python2 :  key 是秘钥 类型为 str msg 要加密的文件 str digestmod 要加密的方式 python3: key 是秘钥 类型为 byte msg 要加密的文件 byte ...

  3. 【例题 8-15 UVA - 12174】Shuffle

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 枚举第一段的范围[0..i] (0<=i<s) 然后看看[i+1..i+s-1],[i+s,i+s+s-1]..这些区间 ...

  4. [Angular] How to get Store state in ngrx Effect

    For example, what you want to do is navgiate from current item to next or previous item. In your com ...

  5. Android Studio配置SVN 以及使用代码管理

    一.Android Studio配置SVN Android Studio关联配置SVN非常easy,在Settings里面.找到Version Control->Subversion.在这个页面 ...

  6. 高性能计算机传奇(vamei)

    高性能计算机是用网络将多台计算机连接在一起.并构成一个统一的系统,从而拥有远超个人电脑的计算能力.这样利用网络,让计算机合作工作的并行系统又称为集群(cluster).server.分布式计算机.超级 ...

  7. Logstash读写性能调整优化

    继续

  8. echarts tooltip提示框 自定义小圆点(颜色、形状和大小等等)

    项目是拿 echarts + 百度地图 来做可视化界面,现在到收尾阶段慢慢优化. 先附代码: formatter: function(params) { var result = '' params. ...

  9. C# Aspect-Oriented Programming(AOP) 利用多种模式实现动态代理

    什么是AOP(Aspect-Oriented Programming)? AOP允许开发者动态地修改静态的OO模型,构造出一个能够不断增长以满足新增需求的系统,就象现实世界中的对象会在其生命周期中不断 ...

  10. Linux系列-安装经常使用软件

    安装JDK: 理论篇: 一.下载JDK 二.安装 ①复制到/usr/java/路径下 [plain] view plaincopy #mkdir /usr/java/ #cp jdk-7u25-lin ...