洛谷 P1381 单词背诵(字符串+哈希表+双指针)

题目描述

洛谷 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] [ij]不能再缩短时,所有满足条件的区间就都被定位过了,只需在这些区间中选一个最短的即可。

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;
    }


}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值