一、本文内容
最长递增子序列的两种动态规划算法实现,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. 【习题 8-3 UVA - 12545】Bits Equalizer

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 如果1的个数第一个串比第2个串多. 那么就无解. 否则. 找几个位置去凑1 优先找'?'然后才是0的位置 剩余的全都用swap操作就 ...

  2. python RESTful API框架:Eve 高速入门

    Eve是一款Python的REST API框架.用于公布高可定制的.全功能的RESTful的Web服务.帮你轻松创建和部署API,本文翻译自Eve官方站点: http://python-eve.org ...

  3. ubuntu网络重启后或主机重启后,/etc/resolv.conf恢复原样的解决办法

    ubuntu网络重启后或主机重启后,/etc/resolv.conf恢复原样的解决办法 /etc/resolv.conf中设置dns之后每次重启该文件会被覆盖,针对这种情况找了一些个解决方法 防止/e ...

  4. 修改android的wifi客户端名称的两种方法

    修改android的wifi客户端名称的两种方法     手机连接到无线路由时,在dhcp的客户端列表里面是这样的名称"android-89425253e5de3a2",这就是安卓 ...

  5. linux设置tab键的宽度为4

    先cd 到~目录 ~$ cd ~$ vi .vimrc set nu

  6. Mysql数据库的瓶颈处理一点建议

    Mysql数据库的瓶颈处理一点建议         我们在使用Mysql数据库是常见的两个瓶颈是CPU和I/O的瓶颈,CPU在饱和的时候一般发生在数据装入内存或从磁盘上读取数据时候.磁盘I/O瓶颈的出 ...

  7. 建立一个 Openshift "Do-It-Yourself" 应用

    建立一个 Openshift "Do-It-Yourself" 应用 Openshift 的  "Do-It-Yourself" 就是自己可以编译定制 WEB ...

  8. Solr 读数据流程

    Solr 读数据流程: 1.用户提供搜索关键词,也就是搜索语句,需要经过分词器处理以及语言处理. 2.对处理之后的关键词,搜索索引找出对应Document 即记录. 3.用户根据需要从找到的Docum ...

  9. Android代码模拟物理、屏幕点击事件

    一.应用中模拟物理和屏幕点击事件 例如,模拟对某个view的点击事件 private void simulateClick(View view, float x, float y) { long do ...

  10. python关于字典的操作

    https://www.cnblogs.com/RENQIWEI1995/p/7931374.html 最常用的代码举例: dict = {'Name': 'Zara', 'Age': 7, 'Cla ...