问题描述:有一个长为n的数列a0,a1,a2........a(n-1)。请求出这个序列中最长的单增子序列的长度。单增子序列的定义是:对于任意的 i<j,都满足ai<aj。
这个问题就是著名的最长单增子序列(LIS)问题。对于这道问题,我们可以利用动态规划来进行求解:假设dp[i]表示以a[i]为末尾的最长单增子序列的长度,则在得到dp[i]时,我们可以这样做:初始化dp[i]为1,利用一个j变量遍历已经访问过的数组a中的值,如果此时a[i]>a[j],表示我们可以在原来的子序列之后加上一个构成一个新的以ai结尾的单增子序列,这时,如果dp[i]的值小于dp[j]+1的值时,我们就将其更新。这样我们就可以得到:时间复杂度为O(n^2)。
dp[i] = max{dp[j] + 1, 1} if j<i and a[j] < a[i]。
#include<iostream>
#define max(a, b) ((a)>(b)?(a):(b))
const int INF = 1000000;
const int n = 6;
int a[n] = {4, 2, 3, 1, 5, 5};
int dp[n];
/**
dp[i]表示以a[i]为末尾的最长单增子序列的长度
*/
int dp1(){
int res = 0;
for (int i = 0; i < n; i++){
dp[i] = 1;
for (int j = 0; j < i; j++)
{
if (a[i] > a[j])
{
dp[i] = max(dp[i], dp[j]+1);
}
}
res = max(dp[i], res);
}
return res;
}
int main(){
printf("%d\n", dp1());
system("pause");
return 0;
}
接下来我们换一种思路来想,如果子序列的长度相同的话,那么取得的末尾值越小就越有优势。基于这种思路我们可以利用这样的假设:
dp[i]表示长度为i+1的上升子序列中末尾元素的最小值。首先我们对dp数组用无穷大INF进行初始化,
对于每一个ai,如果j==0,或者dp[j-1]<a[i],我们就用dp[j] = min(dp[j], a[i])来进行更新。这样我们仍然需要一个两层的循环,但是可以进行优化,对于内层的循环:由于dp数组是单增的(可以用反证法证明,如果i<j,而dp[i] > dp[j],则在以dp[j]结尾的单增子序列中的第i位一定小于dp[i],这样就违背了当初对dp数组的假设),我们可以使用一个二分搜索来找的我们需要更新的位置,即在dp数组中找到大于或等于a[i]且最近的位置,这样就将时间复杂度降低为O(n*logn)。
#include<iostream>
#define max(a, b) ((a)>(b)?(a):(b))
const int INF = 1000000;
const int n = 6;
int a[n] = {4, 2, 3, 1, 5, 5};
int dp[n];
/*
返回dp数组中找到>=target且最近的位置
*/
int binary_search(int target){
int l = -1, r = n, m;
while(l+1 != r){
m = (l+r)/2;
if (dp[m] < target)
l = m;
else
r = m;
}
return r;
}
/**
dp[i]表示长度为i+1的上升子序列中末尾元素的最小值
*/
int dp2(){
for (int i = 0; i < n; i++)
{
dp[i] = INF;
}
for (int i = 0; i < n; i++)
{
int p = binary_search(a[i]);
dp[p] = a[i];
}
return binary_search(INF);
}
int main(){
printf("%d\n", dp2());
system("pause");
return 0;
}