我们可以使用动态规划(dynamic programming)解决这个寻找最长递增子序列问题。
最长递增子序列问题是各个互联网公司都喜欢使用的一道经典面试题。 下面给出这个问题的定义。
(1)何为subsequence?
首先, 我们要知道sequence 元素之前有前后的顺序关系。 例如,
1 3 4 5 和1 4 3 5 是两个不同的sequence, 尽管二者有相同的元素组成的。
对于subsequence, 当然就是从一个给定的sequence中按照从左往右 的顺序抽取出数据出来,不打乱顺序, 组成新的一个sequence, 那么我们称这个sequence是原sequence的subsequence(子序列)。
(2)何为increasing?
就是我们抽取的元素不是随便抽取的。 而是要保证每一个抽取的元素都大于前一个抽取的元素。 这样我们得到的这个新的序列就是increasing subsequence 了。
longest increasing subsequence 问题:
就是从给定的sequnce中, 能够抽取出一个最长的increasing subsequence.
例如, 对于给定的sequence: 1, 3, 2, 5, 9, 这个sequence的长度为5。
不难看出这个sequence的最长的increasing subsequence为: 1,3, 5, 9. (1 < 3 < 5 < 9, 所以当然是increasing(递增的)的)长度为4。 但是一个sequence的LIS(longest increasing subsequence)可能并不是唯一的。
例如上个sequence, 另一个LIS为1, 2, 5, 9。
通常对于LIS问题, 只会要求我们sequence其中一个LIS即可。
要解决LIS问题, 主要有两种算法。
一个算法是动态规划(dynamic programming)算法, 该算法的时间复杂度为O(n^2), 比较简单易懂, 一个是贪婪算法(greedy Algorithm), 时间复杂度为O(n log(n))。
LIS 问题的解法。
我们的问题:
对于一个size 为N的vector D, 找到其LIS。
solution: 只要我们找到D的所有的subsequence, 抽出这些subsequence 中满足increasing的, 然后我们就可以找打LIS了。
所以我们的naive算法如下:
代码如下:
//Problem:
// For a sequence of data: a[0], a[1], ..., a[N], find longest increasing subsequence.
//Example: a[] = {3, 2, 6, 4, 5, 1}
// Increasing Subsequcne: {3, 6}, {2,6}, {2, 4, 5}, {1}
//
//Simple Algorithm:
//for i=[N..1]:
// 1. Find all combination of i numbers; (N!)/(i!)(N-i)! O(exp(N))
// 2. See if there is one sorted increasingly.
//
//
//Algorithm 1:
// for i=[0..N], calculate LIS[i], which is the longest increasing subsequence that ends with a[i]
//LIS[0]: 3
//LIS[1]: 2
//LIS[2]: 2,6
//LIS[3]: 2,4
//LIS[4]: 2,4,5
//LIS[5]: 1
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void prt(vector<int>& arr, string msg = "") {
cout << msg << " ";
for (auto i: arr) {
cout << i << " ";
}
cout << endl;
}
void calc_LIS(vector<int>& D) {
vector< vector<int> > L(D.size()); // The longest increasing subsequence ends with D[i]
L[0].push_back(D[0]);
for (int i=1; i<D.size(); i++) {
for(int j=0; j<i; j++) {
if ( (D[j] < D[i]) && ( L[i].size() < L[j].size() ) ) {
L[i] = L[j];
}
}
L[i].push_back(D[i]);
}
for (auto x: L) {
prt(x);
}
}
int main() {
int a[] = {3, 2, 6, 4, 5, 1};
vector<int> arr(a, a + sizeof(a)/sizeof(a[0]));
prt(arr, "Data In:");
calc_LIS(arr);
return 0;
}
上述算法, 从长度为N的vector 开始所有, 如果子序列长度为N 的D的的subsequence 中有一个increasing subsequence, 然后就退出(break)。 如果没有找到, 找D的长度为N-1 的所有子序列, 只要其中有一个increasing的, 就退出。 一次迭代下去, 最终我们得到的当然是LIS了。
这个算法比较simple, 但是时间复杂度不是很好, 最坏情况下(worst case)为(for loop 循环到底, 搜索到底):
时间复杂度达到指数量级了, 所以可以说该算法效果很差。
所以, 我们需要一个更好的算法。这就要求助于dynamic programming(动态规划)。
所谓的动态规划, 就是将一个复杂的问题break down 为一系列的小的subproblems, 动态规划通过对每个子子问题只求解一次, 将其解保存在一个表格中, 从而无需对每个子子问题都重新计算, 避免了这种不必要的计算工作。 动态规划方法通常用来求解最优化问题。 此类问题有许多可行解, 但是只有一个最优解。 这也是我们需要苦苦寻找的的解。
对于上述的LIS问题, 我们的问题是:
给定一个size 为N的 vector D, 找到其LIS。
上述问题的子问题是(subproblem):
也就是说定义一个vector L, L[i] 也是一个vector。 L[i] is the longest increasing subsequence of D that ends with D[i]。 换句话说, D的所有increasing subsequence that ends with D[i], L[i] is the longest。
不难看出, L[0] = {D[0]}
举个例子:
如下为D:
运算过程为:
解释:
L[5]是 {1}, 因为末尾为D[5](即1)的LIS(当然是increasing)为{1}。
所以只要我们找到所有的L, 我们就很容易找到L最长的为{2, 4, 5}。 这是D的LIS。
同过对问题的分析, 我们有如下的公式:
也就是说对于所有的j, 找到L[0], L[1], ... L[j]中最长的那一个, L[j] 的最后一个元素为D[j], 只要满足j < i, D[j] < D[i] , 我们就选择最长的LIS, 然后将D[j] 加到L[i] 的后面。
也就是说, 如下图:
例如, 当i = 4 时候, 即
L[4] = MAX(...) + "D[4]"
我们从L[4] 看时, L[4] 的末尾为D[4]。 然后我们从L[0], L[1], ...L[3]中选择最长的一个, 即L[3] . 为 {2, 4}, 由于满足D[3] < D[4], 所以我们将D[4] 接在L[3]的后面。 于是我们得到{2, 4, 5}.
上述算法的时间复杂度为O(N^2)
程序如下:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// print a vector
void prt(vector<int>& arr, string msg = "") {
cout << msg << " ";
for (auto i: arr) {
cout << i << " ";
}
cout << endl;
}
void calc_LIS(vector<int>& din, vector<int>& dout) {
vector<int> M; // M[j] store the index of the smallest tail of all LIS of length (j+1)
M.push_back(0); // M[0]
vector<int> P(din.size()); // Predecessor of M[j]
for (int i=0; i < din.size()- 1; i++) { // Calculate M for {din[0]..din[i]}
if (din[M.back()] < din[i+1]) {
M.push_back(i+1);
P[i+1] = M[M.size()-2];
prt(M, "M: ");
continue;
}
vector<int>::iterator pos = lower_bound(M.begin(), M.end(), din[i+1], [&](int m, int val){return (din[m] < val);});
if ( din[*pos] > din[i+1] ) {
*pos = i+1;
if (pos != M.begin())
P[i+1] = *(pos - 1);
}
prt(M, "M: ");
}
int v = M.back();
dout.resize(M.size());
for (int u = M.size(); u!=0; u--) {
dout[u-1] = din[v];
v = P[v];
}
}
int main() {
int a[] = {3, 2, 6, 4, 5, 1};
vector<int> arr(a, a + sizeof(a)/sizeof(a[0]));
vector<int> res;
prt(arr, "Data In:");
calc_LIS(arr, res);
prt(res, "Data out:");
return 0;
}
运行结果为: