2.8滑动窗口专题:最小覆盖子串

1. 题目链接

LeetCode 76. 最小覆盖子串


2. 题目描述

给定字符串 st,要求找到 s最小的窗口,使得该窗口包含 t 的所有字符(包括出现次数)。若不存在,返回空字符串。
示例
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"(最短窗口包含 ABC,且长度最短)。


3. 示例分析

s = "ADOBECODEBANC", t = "ABC" 为例:

  1. 滑动窗口法:

    • right 移动到索引 5 时,窗口 "ADOBEC" 包含 ABC,记录长度为 6。
    • 继续移动 right 到索引 10,窗口缩小为 "BANC",长度为 4,更新为最终结果。
  2. 暴力枚举法:

    • 枚举所有子串,例如 [0,5][0,6][9,12],逐个检查是否包含 t 的所有字符。

4. 算法思路

滑动窗口法

  1. 哈希表统计
    • hash1 统计 t 中字符的出现次数。
    • hash2 统计当前窗口内字符的出现次数。
  2. 动态窗口调整
    • 右指针扩展:将字符加入窗口,若其出现次数等于 t 中的次数,增加有效计数 count
    • 左指针收缩:当窗口包含 t 的所有字符时,尝试缩小窗口并更新最小长度。
  3. 结果记录:每次窗口满足条件时,检查是否为最短窗口。

暴力枚举法

  • 枚举所有可能的子串起点 i 和终点 ji ≤ j),检查子串是否包含 t 的所有字符。

5. 边界条件与注意事项
  1. 字符重复性:窗口需满足 t 中字符的出现次数,而不仅是存在性。
    • 例如 t = "AA",窗口必须包含至少两个 A
  2. 无效输入:若 s 长度小于 t,直接返回空。
  3. 哈希表初始化:需覆盖所有 ASCII 字符(代码中使用 hash[128])。
  4. 最小窗口更新:需在每次窗口合法时实时更新,避免遗漏更优解。

6. 代码实现
class Solution {
public:
    string minWindow(string s, string t) 
    {
        int ns = s.size(), nt = t.size();
        if (ns < nt) return "";

        int hash1[128] = {0}; // 统计t的字符频次
        int kinds = 0;        // t中不同字符的种类数
        for (char ch : t) {
            if (hash1[ch]++ == 0) kinds++;
        }

        int hash2[128] = {0}; // 窗口内字符频次
        int left = 0, count = 0, min_len = INT_MAX, begin = -1;

        for (int right = 0; right < ns; right++) {
            char in_ch = s[right];
            hash2[in_ch]++; 
            // 当窗口内该字符频次首次满足t的要求时,增加count
            if (hash2[in_ch] == hash1[in_ch]) count++;

            // 当窗口包含t的所有字符时,尝试缩小窗口
            while (count == kinds) {
                // 更新最小窗口
                if (right - left + 1 < min_len) {
                    min_len = right - left + 1;
                    begin = left;
                }
                // 移除左边界字符
                char out_ch = s[left++];
                // 若移除后导致频次不足,减少count
                if (hash2[out_ch]-- == hash1[out_ch]) count--;
            }
        }

        return (begin == -1) ? "" : s.substr(begin, min_len);
    }
};

2.8滑动窗口专题:最小覆盖子串_第1张图片


7. 暴力枚举法与滑动窗口法对比图表

对比维度 暴力枚举法 滑动窗口法
核心思想 枚举所有子串,逐一检查是否包含 t 的所有字符。 动态维护窗口,仅在窗口不满足条件时扩展,满足条件时收缩以寻找最优解。
时间复杂度 O(n² * m)(n为s长度,m为t长度,需遍历所有子串并检查字符频次)。 O(n)(每个字符被左右指针各访问一次)。
空间复杂度 O(1)(使用固定大小的哈希表)。 O(1)(哈希表大小为128,常数空间)。
实现方式 双重循环枚举子串,内层循环统计字符频次。 单层循环扩展右指针,内层循环收缩左指针。
适用场景 极小规模数据(n ≤ 100)。 大规模数据(n ≤ 1e5)。
优点 逻辑简单,直接穷举所有可能性。 时间复杂度最优,适用于大规模输入。
缺点 数据规模大时性能极差(n=1e4时需1e8次操作)。 需精确维护哈希表状态(如count增减逻辑)。

你可能感兴趣的:(#1.每日练习,算法,c++,leetcode,滑动窗口,哈希算法)