本题是AcWing 895. 最长上升子序列的数据强化版,数据范围 n <= 1e5
,可知原来 O(n ^ 2)
的做法已经行不通了。因此我们至少应当将时间复杂度控制在 O(nlogn)
以内。
思路:
贪心 + 二分(二分查找思想回顾)
贪心思想:对于 最大上升子序列,结尾元素越小,越 有利于后面接上其他的数,也就 变得更长。
贪心策略:建立一个 q
数组,元素 q[i]
表示 长度为 i
的 LIS 结尾元素 的 最小值,因此我们只需要维护 q
数组即可。
具体措施:
设 原数组为 a
,q
数组中元素 q[i]
表示 长度为 i
的 LIS 结尾元素 的 最小值,q
数组长度为 len
。
首先数组 a
中存入 n
元素,数组 q
用来存结果,最终 数组 q
的长度 len
就是最终的答案。
假如数组 q
现在存了数,当到了数组 a
的第 i
个位置时,首先判断 a[i]>q[len]
,若大于,则直接将这个数添加到数组 q
末尾,即 q[++len]=a[i]
。
当 a[i]<=q[len]
时,我们就用 a[i]
去替代数组 q
中的 大于等于 a[i]
的第一个数,因为在整个过程中 ,数组 q
是一个递增的数组,所以我们可以用 二分查找 在 O(logn)
的时间复杂度下 找到目标位置,然后覆盖,即:q[l] = a[i]
。
用 a[i]
覆盖 q[l]
含义:以 a[i]
为最后一个数 的 LIS,其中元素 个数为 l
个。
遍历完整个数组 a
后 我们就可以得到 最终的结果:len
。
时间复杂度:
O ( n l o g n ) O(nlogn) O(nlogn)
代码:
这里给出三种做法,前两种是本文介绍的做法,第三种有点不一样,但是更好写,推荐比赛时候使用
① 利用 STL
的二分库函数 lower_bound
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int n;
int a[N], q[N];
int main()
{
cin>>n;
for(int i=1; i<=n; ++i) scanf("%d", &a[i]);
int len = 0;
q[++len] = a[1];
for(int i=2; i<=n; ++i)
{
if(a[i]>q[len]) q[++len] = a[i];
else q[lower_bound(q+1, q+len+1, a[i])-q] = a[i];
}
cout<<len<<endl;
return 0;
}
② 手打二分函数
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int n;
int a[N], q[N];
int main()
{
cin>>n;
for(int i=1; i<=n; ++i) scanf("%d", &a[i]);
int len = 0;
q[++len] = a[1];
for(int i=2; i<=n; ++i)
{
if(a[i]>q[len]) q[++len] = a[i];
else
{
int l = 1, r = len;
while(l<r)
{
int mid = l+r>>1;
if(q[mid]>=a[i]) r = mid;
else l = mid+1;
}
q[r] = a[i];
}
}
cout<<len<<endl;
return 0;
}
③ 目前为止感觉很简便的写法 减少了判断 很好理解 思想与上文中略有不同
- 下标从
1
开始
//lower_bound:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int n;
int a[N], q[N];
int main()
{
cin>>n;
for(int i=1; i<=n; ++i) scanf("%d", &a[i]);
int len = 0;
for(int i=1; i<=n; ++i)
{
int t = lower_bound(q+1, q+len+1, a[i]) - q;
q[t] = a[i];
if(t>len) ++len; //查找到了 q 数组末尾元素的下一个位置,说明没找到目标,则将 a[i] 添加至末尾元素的下一个位置,长度 len+1
}
cout<<len<<endl;
return 0;
}
//手打二分:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int n;
int a[N], q[N];
int main()
{
cin>>n;
for(int i=1; i<=n; ++i) scanf("%d", &a[i]);
int len = 0;
for(int i=1; i<=n; ++i)
{
int l = 1, r = len+1;
while(l<r)
{
int mid = l+r>>1;
if(q[mid]>=a[i]) r = mid;
else l = mid+1;
}
q[l] = a[i];
if(l>len) ++len;
}
cout<<len<<endl;
return 0;
}
- 下标从
0
开始
//lower_bound:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int n;
int a[N], q[N];
int main()
{
cin>>n;
for(int i=0; i<n; ++i) scanf("%d", &a[i]);
int len = 0;
for(int i=0; i<n; ++i)
{
int t = lower_bound(q+1, q+len+1, a[i]) - q;
q[t] = a[i];
if(t>len) ++len; //查找到了 q 数组末尾元素的下一个位置,说明没找到目标,则将 a[i] 添加至末尾元素的下一个位置,长度 len+1
}
cout<<len<<endl;
return 0;
}
//手打二分
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int n;
int a[N], q[N];
int main()
{
cin>>n;
for(int i=0; i<n; ++i) scanf("%d", &a[i]);
int len = 0;
for(int i=0; i<n; ++i)
{
int l = 1, r = len+1;
while(l<r)
{
int mid = l+r>>1;
if(q[mid]>=a[i]) r = mid;
else l = mid+1;
}
q[l] = a[i];
if(l>len) ++len;
}
cout<<len<<endl;
return 0;
}