问题描述:有一个长为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; }
时间: 2024-10-10 01:24:35