LeetCode 76. 最小覆盖子串:滑动窗口算法详解

一、 题目描述(题目链接)

给定两个字符串 s 和 t,长度分别是 m 和 n,返回 s 中的最短窗口子串,使得该子串包含 t 中的每一个字符(包括重复字符)。如果没有这样的子串,返回空字符串 ""。

示例:

输入:s = "ADOBECODEBANC", t = "ABC"

输出:"BANC"

二、 核心思路:滑动窗口

这道题要求我们在一个长字符串 s 中寻找一个满足特定条件的连续子串 ,并且要求这个子串尽可能短。面对这种"连续子区间"的最优化问题,滑动窗口是最佳的解题范式。

滑动窗口的核心在于维护两个指针 left 和 right,通过不断调整窗口的大小来寻找最优解:

寻找可行解(右指针扩张):right 指针不断向右移动,扩大窗口,直到窗口内的字符能够覆盖字符串 t 的所有字符。

优化可行解(左指针收缩):当窗口满足条件时,left 指针开始向右移动,缩小窗口。在收缩的过程中,如果窗口依然满足条件,我们就记录下当前窗口的长度,并尝试寻找更短的解。

循环往复:当窗口不再满足条件时,right 继续向右移动,寻找下一个满足条件的窗口。

三、 算法实现细节

为了实现上述思路,我们需要解决几个关键问题:

如何判断窗口包含了 t的所有字符?

  1. 使用两个哈希表(由于字符集是 ASCII,可以直接使用大小为 128 的数组代替哈希表):need 记录 t 中字符的需求量,window 记录当前窗口内字符的数量。
  2. 引入变量 valid,记录当前窗口中已经满足数量要求的字符种类数。
  3. 当 valid == required(t 中不同字符的种类数)时,说明窗口已完全覆盖 t。

如何更新最短子串?

使用 start 和 len 变量,分别记录最短子串的起始索引和长度。每次窗口满足条件时,检查当前长度 right - left 是否小于 len,若小于则更新。

四、 C++ 完整代码

cpp 复制代码
class Solution {
public:
    string minWindow(string s, string t) {
        // 记录 t 中每个字符的需求量
        vector<int> need(128, 0);
        // 记录当前窗口中每个字符的数量
        vector<int> window(128, 0);
        
        // 统计 t 的字符需求 (传统 for 循环)
        for (int i = 0; i < t.size(); i++) {
            need[t[i]]++;
        }
        
        // 计算 t 中不同字符的种类数 (关键步骤)
        int required = 0;
        for (int i = 0; i < 128; i++) {
            if (need[i] > 0) {
                required++;
            }
        }
        
        int left = 0, right = 0;
        int valid = 0; // 记录 window 中已经满足 need 条件的字符种类数
        int start = 0, len = INT_MAX; // 记录最小覆盖子串的起始位置和长度
        
        while (right < s.size()) {
            char c = s[right];
            right++;
            
            // 扩大窗口:处理 s[right]
            if (need[c] > 0) {
                window[c]++;
                // 如果当前窗口中该字符的数量达到了 t 中的需求
                if (window[c] == need[c]) {
                    valid++;
                }
            }
            
            // 收缩窗口:当窗口已经覆盖了 t 中所有字符时
            while (valid == required) { // 注意:这里必须用 required,不能用 need.size()!
                // 更新最小覆盖子串
                if (right - left < len) {
                    start = left;
                    len = right - left;
                }
                
                // 处理 s[left],准备收缩
                char d = s[left];
                left++;
                
                if (need[d] > 0) {
                    // 移出窗口的字符会导致窗口不再满足条件
                    if (window[d] == need[d]) {
                        valid--;
                    }
                    window[d]--;
                }
            }
        }
        
        return len == INT_MAX ? "" : s.substr(start, len);
    }
};

五、 复杂度分析

时间复杂度:O(m + n)。其中 m 是字符串 s 的长度,n 是字符串 t 的长度。虽然代码中有一个嵌套的 while 循环,但 left 和 right 指针都只会从左到右遍历一遍 s,因此内层循环的总执行次数最多为 O(m)。

空间复杂度:O(1)。由于字符集的大小是固定的(这里使用 128 大小的数组),因此所需的空间是常数级别的。

相关推荐
怕浪猫10 小时前
RAG 面试 6 连问,从原理到优化全部覆盖
python·算法·面试
无线通信科研笔记10 小时前
IEEE TVT 2026 论文精读与完整复现|相位误差如何重塑近场 RIS 的幅相响应
论文阅读·人工智能·python·算法·论文笔记
mmmmath_311 小时前
二叉树の递归遍历
算法
All for pursuit.11 小时前
【位运算-1】338.比特位计数
数据结构·c++·算法·leetcode
小师兄吃牛肉12 小时前
C语言中的nullptr有必要吗
c语言·c++·算法
一只小小的芙厨14 小时前
【线性DP】
学习·算法·动态规划
奋发向前wcx14 小时前
CSP-J复赛模拟赛5 王晨旭补题 2026.10.5
算法
罗湖老棍子15 小时前
C Looooops(信息学奥赛一本通- P1640)
算法·数论·同余·裴蜀定理·扩展欧几里得
飘尘18 小时前
从"算子"到"AI Infra":大模型背后看不见的那群人在忙什么
人工智能·算法·面试