最长上升子序列:一个序列,求出一段不断严格上升的部分,不一定要连续。(区别于子串:要求连续)
求最长上升子序列有两种方法,一个是复杂度n*n,一个是复杂度n*log2n。
复杂度n*n的算法:
dp[i]对应为前i个的最长上升子序列。
for(int i=1;i<=字符串的长度;i++)
{
dp[i]=1;//前i个字符的最长上升子序列最少为1,就只有第i项
for(int j=i-1;j>0;j--)
{
if(a[i]>a[j])//如果前面有比i对应位置数字小的
{
dp[i]=max(dp[i],dp[j]+1);//与子问题的关系
}
}
}
复杂度为n*log2n的算法:
需要有一个栈来记录上升子序列(不一定是前i项的最优,但是以访问到的i结点结尾的最优)。对每个位置进行访问,访问到第i个位置的时候,这个时候栈记录的是以i-1结点结尾的最优上升子序列,然后把栈里面比i项大的都出栈,再让i项入栈。此时,栈的大小就是以i结点结尾的最优上升子序列。这里查找i该入栈到栈的那个位置使用了二分。(用数组模拟了栈)
举个例子:以(1 6 8 4 7 5 9)为例
访问到的项 | 栈 |
1 | 1 |
2 | 1 6 |
3 | 1 6 8 |
4 | 1 4 |
5 | 1 4 7 |
6 | 1 4 5 |
7 | 1 4 5 9 |
每次求出来的都是以i结点结尾的最优上升子序列,所以要的出全部最长上升子序列需要取最大值。
二分查找i入栈的位置的代码:
d[i]为模拟栈的数组,这里i插入到栈中,二分找位置快。
int search(int x)
{
int fst=1,lst=n;
while(fst<=lst)
{
int mid=(lst+fst)/2;
if(x>d[mid])
fst=mid+1;
else if(x<d[mid])
lst=mid-1;
else
return mid;
}
return fst;
}
主要代码(调用了上面的二分):
memset(d,inf,sizeof(d));
d[0]=-1;
d[1]=a[1];
//上面初始化
dp1[1]=1;
for(int i=2;i<=n;i++)
{
int j=search(a[i]);
d[j]=a[i];
dp[i]=j;
}
int ans=0;
for(int i=1;i<=n;i++)//取最大值
{
ans=max(dp[i],ans);
}