题目传送门 : -------->点这里点这里<----------
题目大意:
给出一串正整数,个数为N个。1<=N<=100000。求最长单调递增子序列长度。
样例输入:
6 5 2 1 4 5 3 3 1 1 1 4 4 3 2 1
样例输出:
3 1 1
===================================================================
最长单调递增子序列问题的DP朴素算法复杂度为 O(n^2);
然而题目中的 N 最大有10^5 一定是超时,因此可以采用nlogn优化。
优化思路一: DP + 二分查找
#define MAX 100010
A[MAX];//记录数字的数组
DP[MAX];///记录A[i] 的DP 值。
re[MAX];//记录到 第 i 个数字时,最大的子序列长度为 re[i];
set[MAX];//记录DP值为 i 的最小的数 为 set[i];
算法过程与 朴素 的DP算法一致,只是记录了步长一定时最小的数,并使用了二分查找,找出下一个元素的步长。
更新 re[i]; set[DP[i]] = min(set[DP[i],A[i]);
=================================================================================
优化思路二: 贪心 + 二分查找
非常神奇的一个事情,这题可以不使用DP解决。使用贪心算法。
贪心策略:
- 初始化一个数组 d[MAX] 初始化为 0 , 变量 len = 0;
- 读入一个数字 num 。
- 如果num > d[len] , d[len++] = num; 继续 2 。
- 如果num >= d[len] , 使用二分法查看d数组,0 - len 的区域,找出不比num小的最小的数使用num替换他。继续 2 。
- 输出 len . 为 最长子序列长度。
======================================================
其实这个算法与思路一是一样的。思路一种可以发现,其实re数组的大部分空间是浪费的,只要记录当前最长的子序列长度即可。而后的set数组记录的是步长为k时最小的数字为set[k];
事实上可以将d数组将这两个数组的功能合并了,首先d数组中的每一个 d[i] 都是步长为 i + 1 时 的 最小的元素。 其次,长度len又是最长子序列的长度。
为什么我们后面的小的元素可以往前插入呢?
我们最后需要输出的是 子序列的长度 len 而不是子序列的本身,所以往前替换元素不会影响 len 的值。 而如果往前替换元素时发现,新的子序列比原来记录的旧的子序列长,那么也就自动覆盖了原来的序列,当前的len还是最长子序列长。
代码:
1 #include <iostream> 2 #include <stdio.h> 3 #include <string.h> 4 #define MAX 100010 5 6 using namespace std; 7 int A[MAX]; 8 int d[MAX]; 9 //////////////////////////// 10 void init() 11 { 12 memset(d,0,MAX * sizeof(int)); 13 return ; 14 } 15 16 int main() 17 { 18 int N; 19 while(~scanf("%d",&N)) 20 { 21 init(); 22 for(int i = 0 ; i < N ; i++) scanf("%d",&A[i]); 23 int len = 0; 24 for(int i = 0 ; i < N ; i++) 25 { 26 if(d[len] < A[i]) 27 { 28 d[++len] = A[i]; 29 } 30 else if (d[len] == A[i]) continue; 31 else 32 { 33 int l = 0; 34 int r = len; 35 int ans = 0; 36 int key; 37 while(l <= r) 38 { 39 key = (l + r) / 2; 40 if(d[key] == A[i]) 41 { 42 ans = key; 43 break; 44 } 45 if(d[key] > A[i]) 46 { 47 ans = key; 48 r = key - 1; 49 } 50 else 51 { 52 l = key + 1; 53 } 54 } 55 d[ans] = A[i]; 56 } 57 } 58 cout << len << endl; 59 60 } 61 return 0; 62 }