题目描述
洛谷 P1381 单词背诵
灵梦有 n个单词想要背,但她想通过一篇文章中的一段来记住这些单词。
文章由 个单词构成,她想在文章中找出连续的一段,其中包含最多的她想要背的单词(重复的只算一个)。并且在背诵的单词量尽量多的情况下,还要使选出的文章段落尽量短,这样她就可以用尽量短的时间学习尽可能多的单词了。
输入格式
第1行一个数 n,接下来 n 行每行是一个长度不超过 10 的字符串,表示一个要背的单词。
接着是一个数 m,然后是 m行长度不超过 10 的字符串,每个表示文章中的一个单词。
输出格式
输出文件共2行。第1行为文章中最多包含的要背的单词数,第2行表示在文章中包含最多要背单词的最短的连续段的长度。
输入输出样例
输入
3
hot
dog
milk
5
hot
dog
dog
milk
hot
输出
3
3
数据规模与约定
对于 100% 的数据,n≤1000,m≤
1
0
5
10^5
105。
思路
1.将要背的单词编号,并用哈希表存储要背的单词和对应编号的映射关系。
2.读入文章中的 m m m个单词的同时统计最多要背的单词数 c a n can can,并生成数组 f [ ] f[] f[]。用 f [ i ] f[i] f[i]表示:文章中的第 i i i个单词是编号为 f [ i ] f[i] f[i]的要背的单词。若文章中的第 i i i个单词不是要背的单词,则令 f [ i ] = − 1 f[i]=-1 f[i]=−1。
3.用 i i i和 j j j定位这样一个区间 [ i , j ] : [i,j]: [i,j]:文章中的第 i i i个到第 j j j个单词包含了最多要背的单词。题目要求“包含最多要背单词的最短的连续段的长度”,因此只需用 i i i和 j j j定位所有包含了最多能背的单词的区间,再从这些区间中取一个最短的区间即可。(双指针思想)
4.如何定位这样的区间?
初始时将
i
i
i、
j
j
j都指向文章中的第一个单词,用
l
e
n
len
len记录
[
i
,
j
]
[i,j]
[i,j]区间中包含的要背的单词数。后移指针
j
j
j时,
[
i
,
j
]
[i,j]
[i,j]区间中包含的要背的单词数逐渐增加。当
[
i
,
j
]
[i,j]
[i,j]中包含了所有最多要背的单词时,也就定位了第一个满足“包含了最多能背的单词”的区间。此时,考虑可不可以让区间更小:如果第
i
i
i个单词存在于区间
[
i
+
1
,
j
]
[i+1,j]
[i+1,j]中,说明区间可以更短,即
[
i
+
1
,
j
]
[i+1,j]
[i+1,j]也是满足条件的区间。若
[
i
+
1
,
j
]
[i+1,j]
[i+1,j]满足条件,用同样的方法检查区间
[
i
+
1
,
j
]
[i+1,j]
[i+1,j]、
[
i
+
2
,
j
]
[i+2,j]
[i+2,j]、
[
i
+
3
,
j
]
.
.
.
[i+3,j]...
[i+3,j]...可不可以更短。当区间不能更短时,就继续后移指针
j
j
j。当
j
j
j指向文章的末尾并且
[
i
,
j
]
[i,j]
[i,j]不能再缩短时,所有满足条件的区间就都被定位过了,只需在这些区间中选一个最短的即可。
java代码
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
//哈希表want存储要背的单词和对应编号的映射
Map<String,Integer> want = new HashMap<String,Integer>();
int nn=0; //记录要背的单词数目
int n = sc.nextInt();
for (int i = 0; i < n; i++){
String s=sc.next();
if(want.containsKey(s)) continue; //去掉重复单词
//存储要背的单词和对应编号的映射
want.put(s,nn++);
}
//cnt_1[i]记录编号为i的要背的单词是否存在于文章中
//cnt_1[i]=1表示存在,cnt_1[i]=0表示不存在
int []cnt_1 = new int[nn];
int can=0; //记录最多要背的单词数
int m = sc.nextInt();
//f[i]表示文章中的第i个单词是编号为f[i]的要背的单词
int []f = new int[m];
for (int i = 0; i < m; i++){
//用p记录文章中的当前单词的编号
Integer p=want.get(sc.next());
//不是要背的单词
if(p==null) {
f[i]=-1;
continue;
}
f[i]=p; // 文章中第i个单词的编号是p
//统计最多能背的单词数can
if(cnt_1[p]==0) {
cnt_1[p]++;
can++;
}
}
//一个单词都不能背,直接输出答案并返回
if(can==0){
System.out.println(0);
System.out.println(0);
return;
}
/*
cnt_2功能同上述的cnt_1
i,j定位区间
len记录区间[i,j]中包含的最多要背的单词数
Len记录满足条件的最短区间
*/
int []cnt_2 = new int[nn];
int i=0,j=0,len=0,Len=Integer.MAX_VALUE;
while(j<m){
//第j个单词不是要背的单词,直接考虑区间[i,j+1]
if(f[j]==-1) {
j++;
continue;
}
//用len记录区间[i,j]中包含的最多要背的单词数
if(cnt_2[f[j]]==0) {
cnt_2[f[j]]++;
len++;
}
//若len==can,则当前区间包含了最多要背的单词
if(len==can){
Len=Math.min(Len,j-i+1);
//检查是否可以继续缩短区间
while(check(f,i,j)){
i++;
Len=Math.min(Len,j-i+1);
}
}
j++;
}
System.out.println(can);
System.out.println(Len);
}
//check检查区间[i,j]是否可以继续缩短
public static boolean check(int []f ,int i,int j){
for(int x=i+1;x<=j;x++){
if(f[i]==f[x]) return true;
}
return false;
}
}