最长上升子序列 II
luck
![在这里插入图片描述](https://img-blog.csdnimg.cn/d1aa683571744c0986acdb4a56cc39ac.png#pic_center)
- 这道题的话,如果用之前的最长上升子序列的方法做的话,肯定是会超时的。因为之前的动态规划方法的话,时间复杂度是O(N^2),而现在n的最大值有10万,那么肯定会超时。
- 也需要用一种全新的方式去思考。我们定义一个数组q,它的下标1,2,3,4就代表着上升子序列的长度,然后里面的数值代表着当前长度的上升子序列当中最后的一个数字。那么相同长度的上升子序列肯定会有很多个,但在这个长度的上升子序列当中,放到数组q里面的最后一位数字是根据最优选择来的。
int q[N];
- 比方说举个例子,长度为2的上升子序列可以有1,2,也可以有1,3,但是为了后续能够继续把这个上升子序列给他不断的扩展,也就是往屁股后面去添加数字,对于长度为2的这两个上升子序列而言,1,2优于1,3。所以说如果要存长度为2的上升子序列当中最后一位数字的话,肯定存的是2,而不会是3。因为我们存的是该长度的上升子序列当中的最后一位数字是根据最优情况来的,换句话说,是最适配情况来的。
int q[N];
- 然后可以判断出这个数组q,随着这个下标的增长一定是严格单调递增的。因为假设他并不是严格单调递增,中间出现两段是持平或者甚至出现递减的情况,如图所示,就会产生巨大的矛盾,所以说这个数组q必定是向上严格单调递增.
![在这里插入图片描述](https://img-blog.csdnimg.cn/f80ac795f9674830a1079e384d739596.png#pic_center)
- 我们明确了上述状况之后,我们现在依次去遍历一下原数组的数字,然后总体原则是这样的:每次得到一个原数组的数字ar[i]之后,在数组q当中去寻找这么一个位置:小于arr[i]的最右边界(为什么说边界呢?因为我们之前已经说过,这个数组q它一定是严格单调递增的,所以说这个所谓的最右边界是肯定能够找到的),至于这个最右边界该怎么去找,就用二分查找。当然一开始的话i==1就自己特判一下,这个应该懂的.
for (int i=1;i<=n;i++)
{
if (i==1)
{
q[len++]=arr[1];
}
else
{
int l=0;
int r=len-1;
while(l<r)
{
int mid=(l+r+1)/2;
if (q[mid]<arr[i])
{
l=mid;
}
else
{
r=mid-1;
}
}
- 然后接下来到这个份上的话,已经从原数组当中循环拿取了一个数字arr[i],并且在严格单调递增的数组q当中去找到了小于该数字的最右边界,这时候如果此刻的最右边界下标值刚好为q的已有最右边界,这时候就应该把数组q在向右拓展一格,也就是说有了一个新的长度(+1)的上升子序列的最后一位已知最优情况就是arr[i],因此可以暂且去把这个数字放到数组q最右边;当然如果说最右边界下标是在数组q当中,那么此时此刻由于下一位当中的值就是比arr[i]大的,但此刻应该被arr[i]给替换掉,因为他更优。
if (l==len-1)
{
q[len++]=arr[i];
}
else
{
q[l+1]=arr[i];
}
- 然后外层不断循环,依次从原数组当中摘下一个数字来,然后把这个数字放到数组q上面去,这个过程当中有可能这个数组q会不断的向右拓展,然后当循环结束的时候,这个数组q已有的最大长度就是最长的上升子序列的长度。
printf("%d\n",len-1);
总代码
#include <stdio.h>
#define N 100010
int n;
int arr[N];
int q[N];
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d",&arr[i]);
}
q[0]=-1000000007;
int len=1;
for (int i=1;i<=n;i++)
{
if (i==1)
{
q[len++]=arr[1];
}
else
{
int l=0;
int r=len-1;
while(l<r)
{
int mid=(l+r+1)/2;
if (q[mid]<arr[i])
{
l=mid;
}
else
{
r=mid-1;
}
}
if (l==len-1)
{
q[len++]=arr[i];
}
else
{
q[l+1]=arr[i];
}
}
}
printf("%d\n",len-1);
return 0;
}