- 题目描述:
-
给定一个整型数组, 求这个数组的最长严格递增子序列的长度。 譬如序列1 2 2 4 3 的最长严格递增子序列为1,2,4或1,2,3.他们的长度为3。
- 输入:
-
输入可能包含多个测试案例。
对于每个测试案例,输入的第一行为一个整数n(1<=n<=100000):代表将要输入的序列长度
输入的第二行包括n个整数,代表这个数组中的数字。整数均在int范围内。
- 输出:
-
对于每个测试案例,输出其最长严格递增子序列长度。
- 样例输入:
-
4 4 2 1 3 5 1 1 1 1 1
- 样例输出:
-
2 1
【思路分析】
求LIS的经典问题,用到的不是DP里面O(n*n)的方法,而是用了基于贪心、二分查找的O(nlogn)方法(为什么没有命名成XXX算法 = =)。
设a[n]为原序列,d[n]为长度为n的上升子序列的最后一个元素,当有多个长度为n的上升子序列时,取这些子序列中末尾最小的元素作为d[n]的值。为什么选最小的呢?因为最小的最有“潜力”(贪心)。举个例子,假设原序列为:2,1,8,3,7,5,6,对于d[3],长度为3的上升子序列有1,3,7和1,3,5两个,那么d[3]取值为5,即末尾最小的元素。因为在这个子序列后可能存在x满足 5 < x < 7(即x == 6),因此要是子序列确定为1,3,7的话,则显然不如1,3,5,6长。可见,d数组中的元素是单调递增的。
有了上述的贪心策略,便可以进行下面的操作了。首先,令len = 1,d[1] = a[1],当a[i] > d[len]时,有d[++len] = a[i],即加入新的元素来扩充上升子序列;否则,从d[1]到d[len - 1]找到一个j,使得a[i]满足: d[j - 1] < a[i] < d[j],这时有d[j] = a[i],即用a[i]来替换d[j](也就是a[i]比d[j]更有“潜力”)。又由于d数组是单调递增的,因此可以用二分查找O(logn)很快找到j的值。
最后用a = {2,1,5,3,6,4,8,9}这个序列来过一遍上述的流程。首先,len = 1,d[1] = a[1] = 2。则a[i],len,d[len]的值的变化见下表:
代码如下:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn = 100005;
int n;
int a[maxn];
int d[maxn];//记录长度为i的上升子序列最后一个元素的值
int binSearch(int key,int left,int right)
{
while(left <= right)
{
int mid = (left + right) >> 1;
if(key > d[mid] && key <= d[mid + 1])//贪心策略
{
return mid;
}
else if(key > d[mid])
{
left = mid + 1;
}
else
{
right = mid - 1;
}
}
return 0;
}
int LIS(int n)
{
d[1] = a[1];
int len = 1;
int j = 0;
for(int i = 2;i <= n;i++)
{
if(d[len] < a[i])
{
j = ++len;//直接向后插入
}
else
{
j = binSearch(a[i],1,len) + 1;//找到替换位置
}
d[j] = a[i];
}
return len;
}
void init()
{
for(int i = 1;i <= n;i++)
{
scanf("%d",&a[i]);
}
}
void solve()
{
printf("%d\n",LIS(n));
}
int main()
{
while(scanf("%d",&n) != EOF)
{
init();
solve();
}
return 0;
}