(12)LeetCode 76. 最小覆盖子串

LeetCode 76. 最小覆盖子串(滑动窗口详解)

📌 题目描述

给你两个字符串 s 和 t,长度分别为 m 和 n。请你返回 s 中最短的子串 ,使得该子串包含 t 中的所有字符(包括重复字符 )。如果不存在这样的子串,则返回空字符串 ""。

注意:

  • 测试用例保证答案唯一。
  • 如果 s 中没有覆盖 t 的子串,返回 ""。

示例 1:

复制代码
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
解释:最短覆盖子串是 "BANC"(长度为3),虽然 "ADOBEC" 也包含 ABC,但更长。

示例 2:

复制代码
输入:s = "a", t = "a"
输出:"a"

示例 3:

复制代码
输入:s = "a", t = "aa"
输出:""
解释:t 中两个 'a' 都需要被覆盖,但 s 只有一个,故返回 ""。

💡 解题思路

本题是经典的 滑动窗口 问题,可以使用 双指针 + 哈希表 在 O ( m ) O(m) O(m) 时间内解决。

核心思想:

  1. 用哈希表 ht 记录目标字符串 t 中每个字符的出现次数(即"需求量")。
  2. 维护一个滑动窗口 [j, i] ,其中 i 是右指针,j 是左指针。
  3. 用另一个哈希表 hs 记录当前窗口内字符的实际出现次数。
  4. 引入一个计数器 cnt ,记录当前窗口中"有效匹配"的字符个数(即窗口中已经满足 t 需求的字符数量)。
  5. 扩展右指针 ,每次加入新字符,若该字符在窗口中的数量不超过需求量,则 cnt++。
  6. 收缩左指针 ,当窗口包含所有 t 的字符后(即 cnt == t.size()),尝试向右移动左指针,去掉冗余字符,以缩小窗口长度。
  7. 在收缩过程中更新最小窗口。

🧩 代码实现(C++)

cpp 复制代码
class Solution {
public:
    string minWindow(string s, string t) {
        unordered_map<char, int> hs, ht;   // hs: 窗口频次,ht: 目标频次
        for (char c : t) ht[c]++;          // 统计 t 中每个字符的需用量

        string res;                        // 最终答案
        int cnt = 0;                       // 窗口中有效匹配的字符个数

        for (int i = 0, j = 0; i < s.size(); i++) {
            // 1. 扩展右边界,将 s[i] 加入窗口
            hs[s[i]]++;
            if (hs[s[i]] <= ht[s[i]]) cnt++;   // 若该字符的数量未超过需求量,则有效计数+1

            // 2. 收缩左边界:移除窗口左侧所有冗余字符
            while (hs[s[j]] > ht[s[j]]) {
                hs[s[j]]--;   // 移除左边界字符
                j++;          // 左指针右移
            }

            // 3. 如果窗口已经覆盖 t,尝试更新答案
            if (cnt == t.size()) {
                int curLen = i - j + 1;
                if (res.empty() || curLen < res.size()) {
                    res = s.substr(j, curLen);
                }
            }
        }
        return res;
    }
};

🔍 代码逐行详解

1️⃣ 哈希表初始化

cpp 复制代码
unordered_map<char, int> hs, ht;
for (char c : t) ht[c]++;
  • ht 存储了 t 中每个字符的需求次数,例如 t = "AAB",则 ht['A']=2, ht['B']=1。
  • hs 初始为空,用于动态记录当前窗口 [j, i] 内字符出现的次数。

2️⃣ 遍历右指针 i

cpp 复制代码
for (int i = 0, j = 0; i < s.size(); i++) {
    hs[s[i]]++;
    if (hs[s[i]] <= ht[s[i]]) cnt++;
    ...
}
  • 每次将 s[i] 纳入窗口,先更新 hs。
  • 如果该字符在窗口中的数量 没有超过 它在 t 中的需求量,说明这个字符是 有效 的,cnt++。
    例如,t 中需要 2 个 A,窗口已有 1 个 A,再来一个 A 时,hs['A']=2,满足 <= ht['A'],因此 cnt++。若再来第三个 A,hs['A']=3 > 2,则不会增加 cnt。

3️⃣ 收缩左指针 j

cpp 复制代码
while (hs[s[j]] > ht[s[j]]) {
    hs[s[j]]--;
    j++;
}
  • 当窗口已包含所需所有字符时(cnt == t.size()),我们需要 尽可能缩短窗口。
  • 检查左指针 j 指向的字符是否 冗余(即窗口中的数量超过了需求量)。
  • 如果是,则将其移出窗口(频次减1,左指针右移),直到左边界字符不再冗余为止。
  • 此步骤保证了窗口在满足覆盖的前提下,左边界无法再向右移动,即当前窗口是 以 i 为右边界时的最小窗口。

4️⃣ 更新答案

cpp 复制代码
if (cnt == t.size()) {
    if (res.empty() || i - j + 1 < res.size())
        res = s.substr(j, i - j + 1);
}
  • 当 cnt == t.size() 时,表示窗口已经覆盖了 t 的全部字符。
  • 此时比较当前窗口长度是否小于已记录的最短长度,若是则更新 res。

🎯 为什么 cnt == t.size() 能保证覆盖?

  • cnt 统计的是窗口中 有效字符的总数 ,每个有效字符意味着该字符在窗口中的出现次数没有超过它在 t 中的需求量。
  • 当 cnt == t.size() 时,说明窗口中已经累积了恰好等于 t 长度的有效字符,且由于收缩步骤保证了任意字符均不超过需求量,因此 窗口内每个字符的数量正好等于(或大于等于,但冗余已被移除)需求 ,即完全覆盖 t。

📊 复杂度分析

  • 时间复杂度 : O ( m ) O(m) O(m),其中 m m m 是字符串 s 的长度。
    左右指针均只会向右移动,每个字符最多被加入和移出窗口各一次,因此总操作次数为 O ( m ) O(m) O(m)。
  • 空间复杂度 : O ( ∣ Σ ∣ ) O(|\Sigma|) O(∣Σ∣),其中 ∣ Σ ∣ |\Sigma| ∣Σ∣ 是字符集大小(本题通常为 52 个英文字母,或扩展为 256 个 ASCII 字符)。哈希表存储的字符种类有限,可视为 O ( 1 ) O(1) O(1)。

🧪 测试用例验证

输入 输出 解释
s = "ADOBECODEBANC", t = "ABC" "BANC" 最短覆盖子串
s = "a", t = "a" "a" 单字符匹配
s = "a", t = "aa" "" 无法覆盖两个 'a'
s = "aa", t = "aa" "aa" 必须完全匹配两个 'a'
s = "bbaa", t = "aba" "baa" 注意顺序不影响,子串包含即可

💭 总结与扩展

  • 滑动窗口是解决子串/子数组问题的重要技巧,常用于求满足条件的最短/最长区间。
  • 本题的 核心难点 在于如何高效判断窗口是否覆盖 t,以及如何处理重复字符。
  • 使用 双哈希表 + 计数器 是解决此类问题的经典范式,掌握后可轻松应对 LeetCode 438、567 等相似题目。
相关推荐
zyl76516 小时前
FlashAttention 详解:从 GPU 硬件、普通 Attention 到分块计算与 Online Softmax
算法
爱读书的小胖20 小时前
LeetCode 122:买卖股票的最佳时机 II(C)解题分析
算法
结局无敌20 小时前
React Native for OpenHarmony 实战:三方库 react-native-modal-dialog 的鸿蒙化适配指南
深度学习·算法·react native·react.js·harmonyos
ychqsq20 小时前
234.无题
经验分享·职场和发展
OPEN-F20 小时前
ROS2系列教程:上位机建图与导航(原理综合)
算法·机器人
weixin_3077791320 小时前
C++代码实现MATLAB中的fitrtree函数功能
开发语言·c++·算法·matlab
tomwang032220 小时前
路径优化建模、优化算法分类及工程化小结
算法·数学建模·分类
小羊没烦恼!20 小时前
Ajax与JSON的一些总结
android·java·前端·数据库·mysql·算法·okhttp
考研保研资料分享21 小时前
物理保研至中科大:固体物理复习、四场面试与科研追问
面试·职场和发展
2501_9269783321 小时前
人机通信协议--人和机器之间的交互想象
人工智能·经验分享·算法·ai写作