【问题描述】
为准备考试,Jessica开始阅读一本很厚的课本。要想通过考试,必须把课本中所有知识点都掌握。这本书总共有P页,第i页恰好有一个知识点a[i](每个知识点都有一个整数编号)。全书中同一个知识点可能会被多次提到,所以她希望通过阅读其中连续的一些页把所有的知识点都覆盖到。给定每页写到的知识点,请求出要阅读的最少页数。
【输入格式】
第一行一个整数P,表示书本的总页数。接下来的一行,包含P个整数,第i整数表示第i页包含的知识点的编号。
【输出格式】
一个整数,表示需要阅读的最少连续页数。
【输入样例】
5
1 8 8 8 1
【输出样例】
2
【数据范围】
1<=P<=10^6 , 1<=a[i]<=10^9
【分析】
本题要求的是一个最长的子序列,能包含原序列所有的公共元素。
暴力算法:枚举子序列左端点和右端点,再检查。时间复杂度O(P4)(指数为什么打不出来、、、)
跟以前一些题类似的是滑动窗口,但这个题要维护的窗口不是最大值或最小值,而是维护窗口内的最短连续子序列长度,且要包含尽量多的不同元素。可以想到用尺取法来做。
思路:
int L=1,ans=inf,cnt2=0;
cnt表示给出序列内不同元素个数。
cnt2表示当前窗口内不同元素个数。
枚举右端点R,用map来存储每一个元素的出现次数,如果出现cnt2==cnt,与ans相比较。每当右端点扩展1,判断左端点是否重复(左端点位置的移动一定是在左端点元素被重复(出现第2次)后才可能的)
这里附一个hash表的程序,其思路与使用map是大致的。
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cmath>
#include<set>
#include<map>
using namespace std;
const int maxn=1000005;
const int inf=10000005;
int n,cnt=2;
int a[maxn],vis[maxn],b[maxn];
int main()
{
// freopen("in.txt","r",stdin);
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
**//离散化**
memcpy(b,a,sizeof(b));
sort(b+1,b+n+1);
for(int i=2;i<=n;i++)
{
if(b[i]!=b[i-1]) b[cnt++]=b[i];
}
for(int i=1;i<=n;i++)
a[i]=lower_bound(b+1,b+cnt,a[i])-b;
memset(vis,0,sizeof(vis));
int L=1,ans=inf,cnt2=0;
for(int R=1;R<=n;R++)
{
vis[a[R]]++;
if(vis[a[L]]==1)
{
if(vis[a[R]]==1) cnt2++;//新的元素
if(cnt2==cnt-1)//判断 //cnt要多一个
ans=min(ans,R-L+1);
}
else
{
while(vis[a[L]]>1) //删除左端重复元素
{
vis[a[L]]--;
L++;
}
if(cnt2==cnt-1)//判断
ans=min(ans,R-L+1);
}
}
printf("%d",ans);
return 0;
}