最长升序子序列一般有两种解法,一种是经典的动态规划方法,复杂度为O(n^2),另外一种方法则借助栈和二分查找,复杂度为O(nlgn)。
动态规划
设d[i]表示以a[i]结尾的最长升序子序列的长度,则可以得到状态转移方程为d[i]=max{1,max{d[j]}+1},其中j=0,1,2,...i-1并且a[j]<a[i]。用一句话概括思路就是每次计算d[i]时,就要扫描数组a中前i-1个元素,如果遇到a[j]>a[i]则跳过a[j]继续扫描j...i-1的元素,否则如果a[j]<a[i]则用以a[j]为结尾的子序列长度+1就是以a[j]为倒数第二个元素,a[i]为倒数第一个元素的最长子序列长度。之所以要求a[j]<a[i]很明显是因为要求的是升序子序列,之所以max{d[j]}是因为要求的是最长的子序列,所以要找到前i-1个元素中子序列长度最大的然后+1。
栈+二分查找
http://www.slyar.com/blog/longest-ordered-subsequence.html
这种方法很直观,比较好理解,用一句话说就是顺次扫描数组,如果a[i]大于栈顶的元素则入栈,否则在栈中找到第一个大于a[i]的元素t并用a[i]替换t。如
1 7 3 5 9 4 8
栈为1 7 的时候,输入3,则要替换7变为1 3,然后输入5大于栈顶的3,直接入栈变为1 3 5=>1 3 5 9=>1 3 4 9=>1 3 4 8。最后的top就是最长升序子序列的长度。
AC_CODE:
#include <stdio.h>
#define N 1001
//动态规划
int a[N];
int dp[N];
/************************************************************************/
/* 0MS 156K*/
/************************************************************************/
int lis(int n)
{
int i,j,max;
for(i=0;i<n;i++)
dp[i]=1;
for(i=1;i<n;i++)
{
max=-1;
//j从0开始
for(j=0;j<i;j++)
{
if(a[j]>=a[i]) continue;
max=(dp[j]>max)?dp[j]:max;
dp[i]=(max+1>1)?max+1:1;
}
}
max=-1;
for(i=0;i<n;i++)
max=(dp[i]>max)?dp[i]:max;
return max;
}
/************************************************************************/
/* 16MS 156K*/
/************************************************************************/
int a[N];
int stack[N];
int lis(int n)
{
int top,i,l,m,r;
top=-1;
stack[++top]=a[0];
for(i=1;i<n;i++)
{
if(a[i]>stack[top])
stack[++top]=a[i];
else
{
l=0;r=top;
while(l<=r)
{
m=(l+r)>>1;
if(stack[m]<a[i])
l=m+1;
else
r=m-1;
}
stack[l]=a[i];
}
}
return top+1;
}
int main()
{
int n,i;
scanf("%d",&n);
for(i=0;i<n;i++)
scanf("%d",&a[i]);
printf("%d\n",lis(n));
return 0;
}