【信息学竞赛专题】滑动窗口(尺取法)超全详解|C++模板+经典例题+避坑指南

📌 专栏:信息学竞赛基础算法专题

💡 适用人群:NOIP/CSP-J/S 入门-提高组选手、算法零基础刷题党

🔥 算法定位:信奥高频必考、双指针核心算法、区间问题最优解


一、算法前言

在信息学竞赛中,**滑动窗口(又称尺取法)**是解决连续区间问题的核心利器,几乎覆盖数组、字符串、子序列、区间最值等高频题型。

传统暴力枚举区间的时间复杂度为 O(n2)O(n^2)O(n2),面对题目 n≤105n \le 10^5n≤105 的数据范围会直接超时。而滑动窗口通过双指针同向移动 的核心思想,仅需一次遍历即可完成求解,时间复杂度优化至 O(n)O(n)O(n),是竞赛中性价比极高的贪心类算法。

本文将从核心原理、两大题型分类、通用C++模板、竞赛经典例题、高频易错点五个维度,带你彻底吃透滑动窗口,适配所有信奥基础区间题型。


二、滑动窗口核心原理

2.1 核心思想

借助两个指针 left(左边界)right(右边界) 模拟一个动态窗口:

  • right指针:不断右移,扩大窗口,纳入新元素

  • left指针:在窗口不合法/满足最优条件时右移,缩小窗口

  • 窗口状态:全程维护合法区间,实时更新最优答案

核心特性:双指针单向移动,不回头,因此每个元素仅被遍历一次,保证线性复杂度。

2.2 适用场景

满足两个条件即可使用滑动窗口:

  1. 求解连续区间/子数组/子串问题

  2. 窗口的合法性具有单调性(窗口扩大不合法、缩小合法,或反之)

✅ 常见题型:区间最值、无重复子串、最小覆盖子串、定长区间求和、最多翻转区间等

❌ 不适用:非连续区间、区间无单调性的问题


三、滑动窗口两大竞赛题型

信奥中所有滑动窗口题目,均可分为定长窗口不定长窗口两类,对应两套通用模板,覆盖99%基础题型。

3.1 定长滑动窗口(窗口大小固定k)

题型特征:题目明确限定区间长度为固定值k,求该长度下的最优解(最大值、最小值、最大和等)

解题逻辑

  1. 先构建初始大小为k的窗口

  2. 右指针右移纳入新元素

  3. 左指针同步右移,移除窗口过期元素

  4. 每次窗口成型后更新答案

3.2 不定长滑动窗口(窗口大小动态变化)

题型特征 :无固定区间长度,求满足条件的最大/最小合法区间

细分两类:

  • 最大合法窗口:窗口合法时持续扩大,不合法时收缩左边界(如最长无重复子串)

  • 最小合法窗口:窗口合法时持续收缩左边界找最优解,不合法时扩大右边界(如最小覆盖子串)


四、C++竞赛通用模板(可直接套用)

以下模板经过竞赛实战验证,代码简洁、无冗余,适配CSP/NOIP考场,可直接复制修改使用。

4.1 定长窗口通用模板

cpp 复制代码
// n:数组长度 k:固定窗口大小 a[]:目标数组
int left = 0;
int ans = 0; // 根据题目初始化最值
for(int right = 0; right < n; right++){
    // 1. 纳入右侧新元素,更新窗口状态
    // ... 自定义状态更新(求和、计数、去重等)

    // 2. 窗口长度超过k,收缩左边界
    while(right - left + 1 > k){
        // 移除左侧过期元素,更新窗口状态
        // ... 自定义状态回滚
        left++;
    }

    // 3. 窗口成型,更新最优答案
    if(right - left + 1 == k){
        ans = max(ans, 窗口状态); // 或min、sum等
    }
}
cout << ans << endl;

4.2 不定长-最大合法窗口模板

cpp 复制代码
int left = 0;
int ans = 0;
for(int right = 0; right < n; right++){
    // 1. 纳入右元素,更新状态
    // ...

    // 2. 窗口不合法,持续收缩左边界
    while(窗口不合法条件){
        // 移除左元素,回滚状态
        // ...
        left++;
    }

    // 3. 更新最大窗口答案
    ans = max(ans, right - left + 1);
}
cout << ans << endl;

4.3 不定长-最小合法窗口模板

cpp 复制代码
int left = 0;
int ans = INT_MAX; // 最小值问题初始化为极大值
for(int right = 0; right < n; right++){
    // 1. 纳入右元素,更新状态
    // ...

    // 2. 窗口合法时,持续收缩找最小区间
    while(窗口合法条件){
        ans = min(ans, right - left + 1);
        // 移除左元素,回滚状态
        // ...
        left++;
    }
}
if(ans == INT_MAX) cout << 0 << endl; // 无合法区间
else cout << ans << endl;

五、竞赛经典例题精讲(C++ AC代码)

精选3道信奥入门-提高组高频真题,覆盖三类窗口题型,手把手拆解思路。

例题1:洛谷P1886 滑动窗口(定长窗口+单调队列)

题目描述:给定长度为n的序列,求所有长度为k的连续窗口的最大值、最小值。(经典模板题)

解题思路 :定长窗口结合单调队列,维护队列单调性,剔除窗口过期元素,O(n)O(n)O(n) 求解区间最值。

cpp 复制代码
#include <iostream>
#include <deque>
using namespace std;

const int N = 1e6 + 10;
int a[N];
deque<int> q; // 存储数组下标

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n, k;
    cin >> n >> k;
    for(int i = 0; i < n; i++) cin >> a[i];

    // 求最小值(单调递增队列)
    for(int i = 0; i < n; i++){
        // 移除窗口外过期元素
        while(!q.empty() && q.front() <= i - k) q.pop_front();
        // 维护递增单调性
        while(!q.empty() && a[i] <= a[q.back()]) q.pop_back();
        q.push_back(i);
        // 窗口成型输出答案
        if(i >= k - 1) cout << a[q.front()] << " ";
    }
    cout << endl;
    q.clear();

    // 求最大值(单调递减队列)
    for(int i = 0; i < n; i++){
        while(!q.empty() && q.front() <= i - k) q.pop_front();
        while(!q.empty() && a[i] >= a[q.back()]) q.pop_back();
        q.push_back(i);
        if(i >= k - 1) cout << a[q.front()] << " ";
    }
    return 0;
}

例题2:最长无重复字符子串(最大不定长窗口)

题目描述:给定字符串,找出不含重复字符的最长子串长度。

解题思路:窗口内字符无重复为合法条件,出现重复则收缩左边界,动态更新最大窗口长度。

cpp 复制代码
#include <iostream>
#include <string>
#include <cstring>
using namespace std;

int main(){
    string s;
    cin >> s;
    int vis[128] = {0}; // 记录字符出现次数
    int left = 0, maxLen = 0;
    int n = s.size();

    for(int right = 0; right < n; right++){
        char c = s[right];
        vis[c]++;

        // 存在重复字符,收缩左边界
        while(vis[c] > 1){
            vis[s[left]]--;
            left++;
        }

        // 更新最大长度
        maxLen = max(maxLen, right - left + 1);
    }
    cout << maxLen << endl;
    return 0;
}

例题3:最小覆盖子串(最小不定长窗口)

题目描述:给两个字符串s、t,找出s中包含t所有字符的最小子串。

解题思路:右指针扩窗口至包含所有目标字符,再收缩左边界找最小合法区间。

cpp 复制代码
#include <iostream>
#include <string>
#include <cstring>
#include <climits>
using namespace std;

int main(){
    string s, t;
    cin >> s >> t;
    int window[128] = {0}, target[128] = {0};
    int cnt = 0; // 记录匹配的字符种类
    int left = 0, minLen = INT_MAX;
    int n = s.size(), m = t.size();

    // 初始化目标字符计数
    for(char c : t) target[c]++;

    for(int right = 0; right < n; right++){
        char c = s[right];
        window[c]++;

        // 该字符匹配完成,种类+1
        if(target[c] && window[c] == target[c]) cnt++;

        // 完全匹配,收缩左边界找最小区间
        while(cnt == m){
            minLen = min(minLen, right - left + 1);
            char lc = s[left];
            if(target[lc] && window[lc] == target[lc]) cnt--;
            window[lc]--;
            left++;
        }
    }

    if(minLen == INT_MAX) cout << "" << endl;
    else cout << s.substr(left - minLen, minLen) << endl;
    return 0;
}

六、竞赛高频易错点避坑指南

滑动窗口代码简单,但细节极易丢分,总结考场高频错误:

6.1 边界判断错误

窗口长度计算为 right - left + 1,切勿遗漏 +1,这是新手最常见失分点。

6.2 状态更新顺序错误

必须遵循:先扩窗口、再判合法、最后更新答案,顺序颠倒会导致窗口状态错乱。

6.3 最值初始化不当

求最大值初始化为0,求最小值必须初始化为 INT_MAX,同时需判断无合法区间的特判情况。

6.4 数据范围超时

处理 n≥105n \ge 10^5n≥105 数据时,必须添加 ios::sync_with_stdio(false);cin.tie(0); 关闭流同步,避免TLE。

6.5 单调队列过期元素未清除

定长最值窗口中,必须优先清除队头过期下标,再维护队列单调性,否则答案错误。


七、刷题适配总结

  1. 固定区间长度 → 直接套用定长窗口模板(区间和、区间最值、固定子串)

  2. 求最长合法区间 → 最大不定长窗口(无重复、最多翻转、最大和区间)

  3. 求最短合法区间 → 最小不定长窗口(覆盖子串、最小达标区间)

  4. 区间最值进阶 → 滑动窗口+单调队列,解决1e6级大数据问题


八、文末总结

滑动窗口是信息学竞赛中性价比最高的基础算法,逻辑简单、代码短小、效率极高,是冲刺CSP-J/S、NOIP初赛、复赛的必备知识点。

掌握两套核心逻辑+三套通用模板,即可覆盖绝大多数区间类题型,后续刷题只需根据题目条件修改窗口合法判断逻辑即可。

后续会持续更新信奥算法专题:前缀和、差分、二分、单调队列、DP基础,感兴趣可以点赞+关注,持续刷题不迷路!


💖 原创不易,转载请注明出处

✨ 关注博主,持续更新信息学竞赛干货、真题解析、刷题技巧

相关推荐
不会C语言的男孩9 小时前
VS Code 中搭建 C/C++ 开发环境(MSYS2 编译器)
c语言·c++
wjs20249 小时前
JavaScript 类型转换
开发语言
似水এ᭄往昔9 小时前
【Qt】--Qt概述
开发语言·c++·qt
澈2079 小时前
动态规划入门:从斐波那契到爬楼梯
c++·算法
星秀日9 小时前
rust学习入门
开发语言·学习·rust
星越华夏9 小时前
python办公自动化,csv文件/excel文件差集合并
开发语言·python·excel
x_xbx9 小时前
LeetCode:739. 每日温度
算法·leetcode·职场和发展
弹简特9 小时前
【零基础学Python】04-Python运算符、分支、循环与随机数实战教程
开发语言·python
不会C语言的男孩9 小时前
C++ Primer Plus 第3章:处理数据
开发语言·c++