什么是最长上升子序列?从字面意思就很好理解,就是从一系列顺序输入中,寻找一个上升子序列,要求这个子序列的长度最长。对O(n^2)的解法(DP)这里不讨论了,主要说一下nlgn的解法。网上查了一下,都是通过维护一个数组来实现该算法,但是只给出了求最长上升子序列的长度的代码,没有返回要求的子序列。这里就将此方法扩展一下,使得算法可以返回一个满足条件的子序列。好吧,其实在维基上就有该算法的详细介绍和伪码实现(Longest increasing subsequence),我只是代码搬运工,哈哈哈。下面是算法的c++实现(这里给出的是严格最长上升子序列的代码),如有错误,还望不吝赐教。
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
int main(void){
vector<int> array;
#ifndef ONLINE_JUDGE
freopen("d://file.in", "r", stdin);
#endif
int n;
/*输入序列*/
while(cin >> n)
array.push_back(n);
if(array.size() == 0) return 0;
/*
for(auto a : array) cout << " " << a;
cout << endl;
*/
/*
**这两个数组是这个算法实现的关键
**数组f表示对应长度的上升子序列的最小末尾值在array中的下标,比如f[k]表示长度为k+1的上升子序列的最小末尾值在array的下标为f[k]
**数组pre记录了更改数组f时,f的前一个值,比如pre[j]表示当使用array[j]更新数组f时,pre[j]等于要更新的数组f的位置的前一个的值
*/
vector<int> f(array.size(), 0), pre(array.size(), 0);
int top = -1, maxV = 0;
for(int i = 0; i < array.size(); i++){
/*保存数组f中被更新的地址下标*/
int pos;
/*处理array的第一个数或者array[i]的值大于数组f的所有值,需要增加数组范围,末尾位置上保存下标*/
if(i == 0 || array[i] > array[f[top]]){
f[++top] = i;
pos = top;
maxV = max(maxV, top+1);/*更新最长上升子序列的长度*/
//cout << i << ": " << top << " " << f[top] << endl;
}
else{
/*二分查找数组中大于等于某一个指定值的最小位置*/
int L = -1, R = top, mid;
while(R-L > 1){
mid = (L+R)/2;
if(array[f[mid]] >= array[i]) R = mid;
else L = mid;
}
f[R] = i;
pos = R;
}
/*更新数组pre*/
if(pos == 0) pre[i] = -1;
else pre[i] = f[pos-1];
/*
for(int j = 0; j <= top; j++) cout << " " << f[j];
cout << endl;
cout << i << ": " << f[pos] << " " << pre[i] << endl;
*/
}
/*
for(int j = 0; j <= top; j++) cout << " " << f[j];
cout << endl;
for(auto a : pre) cout << " " << a;
cout << endl;
*/
cout << maxV << endl;
/*根据数组pre重构出最长上升子序列*/
int index = f[top];
vector<int> res;
for(int i = top; i >= 0; i--){
res.insert(res.begin(), array[index]);
index = pre[index];
}
for(auto a : res) cout << " " << a;
cout << endl;
return 0;
}
</span>
上面的算法用到了两个数组:数组f和数组pre
- 数组f表示对应长度的上升子序列的最小末尾值在array中的下标,比如f[k]表示长度为k+1的上升子序列的最小末尾值在array的下标为f[k]。
- 数组pre记录了更改数组时,f的前一个值,比如pre[j]表示当使用array[j]更新数组f时,pre[j]等于要更新的数组f的位置的前一个的值。
当处理到序列的array[i] 时,算法更新数组f中的值,是查找数组中的下标k对应的array[k]大于等于array[i]的在数组f中的最小位置。真的好拗口,其实就是说为了找最长上升子序列,我们要子序列的前面部分尽可能的小,也就是所谓的对应长度的上升子序列的最小末尾值。至于数组pre是保存了数组f的更新状态,其实在更新数组f时一定已经出现了满足要求的数组,但是可能因为后面的输入又把某些值更改了。好费劲,来个实例吧。
例如:1 3 4 9 15 2 3 9
第0次:处理array[0]
数组下标:0 1 2 3 4 5 6 7
数组f:0
数组pre:-1
第1次:处理array[1]
数组下标:0 1 2 3 4 5 6 7
数组f:0 1
数组pre:-1 0
第2次:处理array[2]
数组下标:0 1 2 3 4 5 6 7
数组f:0 1 2
数组pre:-1 0 1
第3次:处理array[3]
数组下标:0 1 2 3 4 5 6 7
数组f:0 1 2 3
数组pre:-1 0 1 2
第4次:处理array[4]
数组下标:0 1 2 3 4 5 6 7
数组f:0 1 2 3 4
数组pre:-1 0 1 2 3
第5次:处理array[5]
数组下标:0 1 2 3 4 5 6 7
数组f:0 5 2 3 4
数组pre:-1 0 1 2 3 0
第6次:处理array[6]
数组下标:0 1 2 3 4 5 6 7
数组f:0 5 6 3 4
数组pre:-1 0 1 2 3 0 5
第7次:处理array[7]
数组下标:0 1 2 3 4 5 6 7
数组f:0 5 6 7 4
数组pre:-1 0 1 2 3 0 5 6
数组f的最后位置的内容为最长子序列值的末尾值的在array中的下标,以此为起点来重构出子序列。
给出其他几个测例:
1 2 3 4 7 7 7 8 8 8 :
maxV:6
子序列:1 2 3 4 7 8
f:0 1 2 3 6 9
pre:-1 0 1 2 3 3 3 6 6 6
1 1 1 1 1 1 1 1 1 1:
maxV:1
子序列:1
f:9
pre:-1 -1 -1 -1 -1 -1 -1 -1 -1
maxV:5
子序列:1 2 3 9 14
f:0 5 6 7 8
pre:-1 0 1 2 3 0 5 6 7
maxV:5
子序列:1 2 3 9 15
f:0 5 6 7 8
pre:-1 0 1 2 3 0 5 6 7