They Are Everywhere(Codeforces- P701C)

年轻的宝可梦教练谢尔盖·B.发现了一栋大房子,由n 个公寓按从左到右的顺序排列组成。每个公寓都可以从街上进入,也可以从每个公寓出去。此外,每个公寓都与左边的公寓和右边的公寓相连。第1号公寓只与第2号公寓相连,第n 号公寓只与第n - 1号公寓相连。

每个公寓里都有一种类型的宝可榜。谢尔盖·B.请求房子的居民让他按顺序进入他们的公寓来捕捉宝可榜。在与房子的居民商量后,他们决定让谢尔盖·B.从街上进入一个公寓,访问几个公寓,然后从某个公寓出去。但他们不会让他访问同一个公寓超过一次。

谢尔盖·B.非常高兴,现在他想尽可能少地访问公寓,以收集出现在这栋房子里的所有宝可榜。你的任务是帮助他确定他必须访问的最少公寓数量。

输入

第一行包含整数n (1 ≤ n ≤ 100 000) --- 房子里的公寓数量。

第二行包含长度为n 的行s ,由英文字母的大写和小写组成,第i 个字母表示在第i号公寓里的宝可榜的类型。

输出

输出谢尔盖·B.应该访问的最少公寓数量,以便捕捉到房子里出现的所有类型的宝可榜。

示例 1

Inputcopy Outputcopy
3 AaA 2

示例 2

Inputcopy Outputcopy
7 bcAAcbc 3

示例 3

Inputcopy Outputcopy
6 aaBCCe 5

注意

在第一个测试中,谢尔盖·B.可以从第1号公寓开始,例如,然后在第2号公寓结束。

在第二个测试中,谢尔盖·B.可以从第4号公寓开始,然后在第6号公寓结束。

在第三个测试中,谢尔盖·B.必须从第2号公寓开始,然后在第6号公寓结束。

一、 题目分析

【题目大意】

有n个公寓排成一排,每个公寓里有一只特定类型的宝可梦(用大小写英文字母表示)。谢尔盖希望连续访问一段公寓,确保能抓到这栋楼里出现的所有种类 的宝可梦,并且要求访问的公寓数量最少

【核心等价转化】

把题目剥开,这其实是一道纯粹的字符串问题:

给定一个字符串s,求它的一个最短的连续子串,使得这个子串包含了s中出现过的所有不同的字符。

【物理模型匹配】

看到"连续子串"和"最短/最长",我们应该立刻想到------滑动窗口.

  • 扩张(右指针向右): 当我们还没凑齐所有种类的宝可梦时,我们只能硬着头皮继续往右走,把新的公寓纳入窗口。

  • 收缩(左指针向右): 当我们已经凑齐了所有种类时,我们要考虑:最左边的那间公寓是不是多余的?能不能把它踢掉以缩短总长度?


二、 思考过程与算法设计

解决这个问题,我们需要分两步走:

第一步:全图开视野(明确目标)

我们首先需要遍历一次整个字符串,用一个哈希表(或 ASCII 数组)统计出一共有多少种不同的宝可梦。记为cnt。这就是我们滑动窗口需要达成的"KPI"。

第二步:滑动窗口动态结算

有了目标cnt,我们就可以派出左右两个指针lr去拉尺子了:

  1. 右指针 r 不断向右探索,将路过的宝可梦种类记录下来。

  2. 一旦当前窗口内的宝可梦种类数达到了cnt,说明当前窗口是合法的。

  3. 此时,我们记录下当前窗口的长度 r-l+1,更新最小长度mi

  4. 为了寻找更短 的可能,我们强制让左指针l向右收缩,吐出最左边的宝可梦,看看吐掉之后是不是依然合法。重复这个过程。


三、 时空复杂度分析

  • 时间复杂度: O(N)。在这个过程中,无论是左指针还是右指针,都只会一直向右走,绝对不会回头。每个字符最多进入窗口一次,离开窗口一次。完美线性复杂度!

  • 空间复杂度: O()。由于宝可梦的类型仅由大小写字母组成,ASCII 码的范围在128以内。我们只需要开两个大小为 200 的整型数组来充当哈希表,空间消耗微乎其微,约等于 O(1)。


四、 易错点总结

  1. 大小写敏感:题目明确说明是由大写和小写字母组成,所以 'A' 和 'a' 是两种不同的宝可梦。使用数组充当哈希表时,数组大小至少要开到 128(推荐开 200 确保安全)。

  2. 无穷大初始化 :既然是求"最小值",记录答案的变量mi一定要初始化为一个极大的数(如 0x3f3f3f3f或n),千万不能初始化为0。

  3. 内层循环的越界 :右指针向右滑动的过程中,务必时刻检查r<s.size(),防止数组越界引发错误。


五、 标程注释与框架对比

这里为大家提供两种实现思路。第一种直觉写法,第二种是工程上的黄金模板。

版本一:定左探右法(物理模拟的直觉呈现)

这套代码以左端点i为外层主循环,内部用while驱动右端点探索,极其生动地模拟了"拉尺子"的过程:

cpp 复制代码
//CodeForces - 701C
#include <iostream>
#include <cstring>
using namespace std;
int n;
string s;//s[i]表示在第i号公寓里的宝可榜的类型
int a[200];//记录每种类型宝可棒出现多少次
int cnt;//记录一共有多少种宝可榜
int cnt2;//记录进入的公寓内总共有多少种宝可棒
int b[200];//记录进入的公寓内每种宝可榜收集多少次
int mi=0x3f3f3f3f;//记录必须访问的最小公寓数量

int main(){
    cin>>n;
    cin>>s;
    //记录每一种宝可榜出现多少次及总共多少种
    for(int i=0;i<s.size();i++){
        //如果该类型未出现过 就把类型数加一
        if(a[s[i]]==0)
            cnt++;
        a[s[i]]++;//然后出现次数加一
    }
    int r=0;//从第r间公寓出去(右端点,实际为r+1)
    //从第i间公寓进入(实际为i+1)
    for(int i=0;i<s.size();i++){
        //向右缩小左边界
        if(i>0){
        //当从第i间公寓进入时第i-1间收集的宝可榜就要减掉
        //所以至少要从最左边+1的公寓开始减(即i>0)
        //即如果左端点右移,需要把前一间公寓的宝可梦吐出来
            b[s[i-1]]--;
        //当该类型收集次数为0时
        //进入的公寓内收集种类减1
            if(b[s[i-1]]==0) cnt2--;
        }
        //内部不断拉长右端点,直到凑齐所有种类
        while(r<s.size()){
            //如果第r间里的宝可梦尚未收集过
            //就把进入公寓内宝可榜收集种类+1
            if(b[s[r]]==0) cnt2++;
            //然后该种宝可棒收集次数+1
            b[s[r]]++;
            //如果进入的公寓内宝可棒出现总类少于所有种类,就仅扩大右边界
            if(cnt2<cnt) r++;
            else{//收集种类数等于总种类数
                r++;//边界向右移动一个备用(不然下一轮会重复访问公寓)
                break;//退出
            }
        }
        //现在可以更新一下最小进入公寓数
        if(cnt2==cnt) mi=min(mi,r-i);
    }
    cout<<mi;
    return 0;
}

版本二:定右缩左(滑动窗口的"黄金模板")

版本一的逻辑非常严密,但内外循环的跳转稍显复杂。

在算法竞赛中,我们更推崇"外层无脑进,内层看情况缩"的流水线结构,这样极不容易出现越界和死循环:

cpp 复制代码
//CodeForces - 701C
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

int n,cnt=0,current_cnt=0;
int total_map[200],window_map[200];
string s;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin>>n>>s;
    for(char c:s) {
        if(total_map[c]==0) cnt++;
        total_map[c]++;
    }
    int mi=n; //答案最大就是 n
    int l=0;  //左指针
    //外层永远是右指针r负责吃进新元素
    for(int r=0;r<n;r++){
        //无脑吃进右边的新宝可榜
        if (window_map[s[r]]==0) current_cnt++;
        window_map[s[r]]++;
        //内层:只要凑齐了,就不停地尝试吐掉左边的冗余元素
        while(current_cnt==cnt) {
            //满足条件,更新最小值
            mi=min(mi,r-l+1);
            //左指针吐出元素,开始收缩
            window_map[s[l]]--;
            if(window_map[s[l]]==0) {
                current_cnt--; // 某种宝可榜被彻底吐光了,循环将被打破
            }
            l++;
        }
    }
    cout<<mi;
    return 0;
}
相关推荐
2301_822703207 小时前
Flutter 框架跨平台鸿蒙开发 - 创意声音合成器应用
算法·flutter·华为·harmonyos·鸿蒙
cmpxr_7 小时前
【C】数组名、函数名的特殊
c语言·算法
KAU的云实验台7 小时前
【算法精解】AIR期刊算法IAGWO:引入速度概念与逆多元二次权重,可应对高维/工程问题(附Matlab源码)
开发语言·算法·matlab
会编程的土豆7 小时前
【数据结构与算法】再次全面了解LCS底层
开发语言·数据结构·c++·算法
大熊背8 小时前
如何利用Lv值实现三级降帧
算法·自动曝光·lv·isppipeline
大尚来也8 小时前
驾驭并发:.NET多线程编程的挑战与破局之道
java·前端·算法
向阳而生,一路生花8 小时前
深入浅出 JDK7 HashMap 源码分析
算法·哈希算法
君义_noip9 小时前
信息学奥赛一本通 4150:【GESP2509七级】⾦币收集 | 洛谷 P14078 [GESP202509 七级] 金币收集
c++·算法·gesp·信息学奥赛·csp-s
摸个小yu9 小时前
【力扣LeetCode热题h100】链表、二叉树
算法·leetcode·链表
汀、人工智能9 小时前
[特殊字符] 第93课:太平洋大西洋水流问题
数据结构·算法·数据库架构·图论·bfs·太平洋大西洋水流问题