[csp模拟2]T2——HRZ学英语(尺取法)

题意

在这里插入图片描述

Input

在这里插入图片描述

Output

在这里插入图片描述

输入样例

ABC??FGHIJK???OPQR?TUVWXY?


AABCDEFGHIJKLMNOPQRSTUVW??M

输出样例

ABCDEFGHIJKLMNOPQRSTUVWXYZ


-1

提示

在这里插入图片描述
在这里插入图片描述


分析

第二题最开始看着有点懵,但是做起来就发现不难。这道题可以运用尺取法来解决。


  • 分析题目

提炼信息,根据题意可知:

我们需要在一个字母序列中找到一段连续的序列,其满足:

  • 区间长度为26
  • 区间中恰好包含所有英文字母
  • 区间中可以出现" ?",并且可以代替表示任何字母

显然,本题的关键是在一个序列中寻找其中的一段符合要求的连续区间。毫不犹豫,这完全符合经典的尺取法👉[week5]平衡字符串——尺取法


  • 尺取法的基本设计

既然是尺取法,那么我们就要关注尺取法的几个关键问题:

  • 什么条件下右指针前移
  • 什么条件下左指针前移
  • 什么条件下当前左右指针所含区间为答案
  • 什么条件下结束遍历

1. 右指针前移的条件

右指针一般是在当前新加入的元素仍然满足要求时继续前移。因此若没有出现重复字母,则代表可以继续前移。

  • 当未出现重复字母时

我们所要求的区间中不能包含重复出现的确定的字母。

也就是说出现" ? "时,我们默认是出现了不同于区间中已有的所有字母,但是若重复出现确认的字母,则当前区间不符合要求。

因此,当若右指针前移后指向的新字母在区间中未出现过,或是指向的是" ? "时,则右指针可以前移。

  • 左右指针重叠

若当前左右指针重叠,则右指针前移。

  • ❗️未到达边界时

若当前右指针没有到达序列末尾,则仍然可以移动。若右指针超出了序列末尾,则需要将其规范到序列末尾。

2. 左指针前移的条件

一般若当前区间不再符合要求时,左指针需要前移,直到区间符合要求。

  • 若出现重复字母

若当前区间内存在重复字母,则应该前移左指针。

3. 可以作为答案的区间

在左右指针遍历过程中提及的符合要求的区间,实际上是指的没有违法的区间,也就是可能在之后被最终答案包含的小区间【潜力股233】。

而如果存在答案,则遍历的过程中,一定会出现符合答案要求的区间。但是在遍历过程中,可能会出现多个答案。

不过,根据题目可知,答案取最左序列。也就是说,从左至右遍历遇到的第一个符合要求的序列即为备选答案。

但是题目还说要字典序最小,这是否会影响到尺取法?答案是不会。字典序最小是仅针对区间中最左且包含" ? “的序列,因为一旦包含” ? ",则代表同一个序列有多种取法,但是这已经和尺取法无关了。

那么在本道题的尺取法中该如何判定?何时判定呢?

  • 当左右指针包含区间为26时

由于答案要求区间长度为26,因此若一段连续序列合法,则左右指针之间的区间长度一定能达到26。否则一旦不合法,左指针就会前移,区间长度就会缩短。

因此只要出现该情况,则代表此时的区间即为符合要求的答案。

  • 第一次遇到符合要求的区间

根据题目我们需要选最左出现的答案,本质上就是我们在遍历中遇到的第一个备选答案。所以当我们第一次遇到上述情况时,就可以直接将其选作答案,并结束尺取法了。

4. 何时结束尺取法

根据第3点可知,当我们获得答案时,即可结束尺取法。

除此之外,假设右指针一直走到序列末尾时,若左指针前移直到两者之间的区间小于26,此时区间一定不合法,不需要遍历。因此,规定左指针到序列末尾的距离大于等于26即可。


  • 尺取法的具体实现

在本道题的尺取法中,我们需要解决如何标记字母是否出现以及出现" ? "情况的问题。

在我的代码中,设置了一个bool型数组,数组中每个元素对应ascii码减去65的差等于其下标的字母。true为在区间中已出现,false为没有出现。

【小tip:A-Z在ascii码中是连续的,从A开始依次增加1。A为65。】

  • 指针的移动

在右指针前移时,若前移后的右指针指向的字母未出现过,则将其指向的字母标记为true,否则左指针前移;若出现的是" ? “,则标记其数量+1。
在左指针前移时,将左指针指向的字母重新标记为false后再前移;若指向的是” ? ",则问号数量-1。

  • 合法区间的标记

若当前区间为合法答案,则记录下当前区间的左指针即可,因为长度固定已知,只需要从记录的起点向后遍历固定个。


  • 取最小字典序

这也不难。首先我们可以设置一个char类型的数组,按升序依次存入所有大写字母。

从标记答案的起始处开始遍历序列,依次输出字母。若第一次遇到" ? “,则从头开始遍历字母数组,若当前字母已出现则跳过,将第一个遇到的未出现的字母输出,并标记为已出现。继续遍历序列,再遇到” ? "时,从上一次所取字母的下一个开始遍历。重复此操作,直到遍历完序列上长度为26的连续区间。

当从最小的字母开始遍历扫描未出现的字母时,最先遇到的一定是当前没有出现过的最小的字母,则这样的组合一定满足最小字典序答案。而在此之前所有遍历过的字母都一定出现过,所以之后再寻找未出现的字母时,只需要从上一次找到的字母之后开始寻找。


总结

  1. 这道题比较简单和经典,思路很快就来了。但是调试和修改的时间还是比较长,希望以后可以越来越熟练🤗不过调试的时间还是没白费,再一次证明了耐心和细心和重要

代码

//
//  main.cpp
//  lab2
//
//

#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
#include <deque>
using namespace std;

string s;
char letter[26]=
{'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'};
bool appear[26] = {false};      //标记当前所取区间中的字符出现情况
//vector<int> ans;                //标记所有符合要求的答案

int main()
{
    ios::sync_with_stdio(false);
    
    for( int i = 0 ; i < 26 ; i++ )
        appear[i] = false;
    
    cin>>s;
    
    int n = s.size(),l = 0,r = 0,number = 0,ans = -1;
    
/*    for( int i = 0 ; i < s.size() ; i++ )
        cout<<s[i]<<" ";
    cout<<n<<" !! "<<endl;*/
    
    appear[(int)s[0] - 65] = true;      //标记第一个字符已出现
    
    while ( ( n - 1 ) - l >= 25 )       //若左指针到数组最右的长度已经小于26时,可以退出
    {
        if( r == l )
            r++;
        
//        cout<<s[r]<<" | "<<l<<" "<<r<<" ** "<<endl;
        
        if( s[r] != '?' )                   //如果当前为字符
        {
            if( appear[(int)s[r] - 65] )        //如果当前扫描的字符已经出现过
            {
                if( s[l] != '?' )                //若左指针为字符
                {
                    appear[(int)s[l] - 65] = false;      //则将其所指字符移出所选
//                    sum--;                                  //所选字符数-1
                }
                else                                //否则将所选问号数量-1
                    number--;
                
                l++;                         //左指标右移
            }
            else                                    //如果当前扫描的字符未出现过
            {
                appear[(int)s[r] - 65] = true;          //标记为已出现
                r++;                    //右指标继续右移
            }
        }
        else            //如果当前为问号
        {
            number++;           //则所选问号数量+1
            r++;                    //右指标继续右移
        }
    
        
        //先移动指针,所以当前右指针所指符号其实还未被选
        //即若已出现满足要求的字符串时,左指针到当前右指针的前一个为该字符串
        if( r - l == 26 )        //若已选区间大小为26
        {
            //由于指针是从左到右依次遍历,所以第一次遇到的答案一定最小,遇到即可退出
            ans = l;
            break;
        }
        
        if( r > n - 1 )             //约束右指针的边界
            r == n - 1;
        
    }
    
    if( ans == -1 )             //说明没有答案
        cout<<-1<<endl;
    else                        //有答案时
    {
        for( int i = ans ; i <= ans + 25 ; i++ )        //从字符串起始点开始输出
        {
            if( s[i] != '?' )           //若不为问号则输出
                cout<<s[i];
            else                        //若为问号,则遍历字母数组
            {
                for( int j = 0 ; j < 26 ; j++ )
                {
                    if( appear[j] )     //如果当前字母已出现则跳过
                        continue;
                    else                //永远优先输出第一个遇到的未出现字母
                    {
                        cout<<letter[j];
                        appear[j] = true;           //并将其标记为已出现
                        break;
                    }
                }
            }
        }
    }
    
    
    
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天翊藉君

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值