1. 题目
2. 算法:
1)思路:
采用二分的方式,找出序列 f[] 中,最接近且小于 即将插入数字a[i] 的位置f[r],将a[i]插入或代替在f[r+1]中
2)步骤:
①初始化+定义数组:
· a[] 输入序列
· f[i] 以i为结尾的最长上升子序列长度
· n 输入序列长度
· len (输出)最长子序列的长度
· 初始化 f[ ] 为最大值 0x3f
· l 当前最长上升子序列位置下标最左端 初始值为0
r 当前最长上升子序列位置下标最右端
mid 当前最长上升子序列位置下标中间
②二分
从前往后找 f[]中最接近且小于a[i]的数
(朴素版本:动规 一看就懂)
for(int i=1;i<=n;i++)
{
f[i]=1;
for(int j=1;j<=i-1;j++)
{
if(a[j]<a[i])
{
f[i]=max(f[i],f[j]+1);//比较原来和加上这个数的序列的长短,选更长的
}
}
}
但是因为两重循环,时间复杂度O(n²) 会超,且 f[] 存最长上升子序列,为单调递增序列,所以我们采用更高效的二分来解决
(二分)核心代码:
for(int i=1;i<=n;i++)
{
int l=0,r=len,mid;
while(l<r)
{
mid=(l+r+1)/2;
if(a[i]>f[mid]) l=mid;
else r=mid-1;
}
len=max(len,r+1);
f[r+1]=a[i];
}
大概过程如下(如图:题目样例)
1. 我们希望前面的数字在同样的情况下,尽可能地小,这样后面的数字就尽可能地多。
e.g. 1 5 3 4
若 f[1]=1 f[2]=5 则后面没有数字可以接上,len1=2;
若f[1]=1 f[2]=3 则后面还可以接4 len2=3 >len1
以此类推
2. 如果当前a[i] 小于f[mid] (从小到大),则说明a[i] 存在于f[mid] 左侧,所以将右边界移到mid-1,在f[]左侧找a[i];如果当前a[i] 大于f[mid], 则说明a[i]存在于f[mid]右侧,所以将左边界移到mid处,在f[]右侧找a[i]。
if(a[i]>f[mid]) l=mid;
else r=mid-1;
3. 为什么mid=(l+r+1)/2而不是(l+r)/2?
会死循环!
若不加1 结果如下:
e.g. 1 4 5 7 9
l=1时 r=2 mid=(1+2)/2=1
此时 f[mid]=1,a[2]=4>f[mid]
∴l=mid=1
我们发现会一直如此循环,因为新的l与原来的l相同,mid,r 均不发生改变,导致了死循环
③输出:len
cout<<len;
3. 完整代码:
二分:(满分)
#include<bits/stdc++.h>
using namespace std;
int a[100005],f[100005];
int len=0;
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
memset(f,0x3f,sizeof(f));
for(int i=1;i<=n;i++)
{
int l=0,r=len,mid;
while(l<r)
{
mid=(l+r+1)/2;
if(a[i]>f[mid]) l=mid;
else r=mid-1;
}
len=max(len,r+1);
f[r+1]=a[i];
}
cout<<len;
return 0;
}
朴素动规版:(40分)
#include<bits/stdc++.h>
using namespace std;
int f[5010];
int a[5005];
int main()
{
f[1]=1;
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i-1;j++)
{
if(a[j]<a[i])
{
f[i]=max(f[i],f[j]+1);
}
}
}
int maxn=-0x3f;
for(int i=1;i<=n;i++)
{
if(f[i]>maxn) maxn=f[i];
}
cout<<maxn;
return 0;
}