每日算法练习:LeetCode 76. 最小覆盖子串 ✅

大家好,我是你们的算法小伙伴。今天我们来攻克一道滑动窗口的经典困难题 ------LeetCode 76. 最小覆盖子串。这道题是滑动窗口的「标杆题目」,完美体现了「右指针扩张、左指针收缩」的核心思想,同时考察哈希表计数、匹配状态维护等综合能力,是面试中字符串处理的高频难题。


题目描述

给你两个字符串 s 和 t ,返回 s 中涵盖 t 所有字符的最小长度子串 。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 ""。

注意:

  • 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
  • 如果 s 中存在这样的子串,我们保证它是唯一的答案。
  • 进阶:设计一个在 O(m + n) 时间复杂度内解决此问题的算法。

示例 1:

复制代码
输入:s = "ADOBECODEBANC", t = "ABC"

输出:"BANC"

解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。

示例 2:

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

输出:"a"

解释:整个字符串 s 是最小覆盖子串。

示例 3:

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

输出:""

解释:t 中两个字符 'a' 均应包含在 s 的子串中,因此没有符合条件的子串,返回空字符串。

提示:

  • m == s.length
  • n == t.length
  • 1 <= m, n <= 10^5
  • s 和 t 由英文字母组成

解题思路

核心思路:滑动窗口 + 双哈希表

这道题的核心是动态维护一个窗口 ,保证窗口内包含 t 的所有字符(次数不低于 t 中的次数),并在所有合法窗口中找到长度最小的那个。

核心步骤:
  1. 统计目标字符 :用数组 need 统计 t 中每个字符需要的次数
  2. 计算字符种类 :required = t 中不同字符的种类数量
  3. 右指针扩张:不断加入字符,更新窗口内计数
  4. 匹配判断 :用 match 记录已满足次数要求的字符种类数
  5. 左指针收缩 :当 match == required 时,不断收缩左指针,更新最小窗口
  6. 返回结果:最终截取最小窗口子串

代码实现(O (m+n) 时间复杂度)

复制代码
class Solution {
    public String minWindow(String s, String t) {
        if (t.length() > s.length()) {
            return "";
        }

        int[] need = new int[128];
        int[] window = new int[128];

        // 统计t中每个字符的出现次数
        for (char c : t.toCharArray()) {
            need[c]++;
        }

        // 计算t中不同字符的种类数
        int required = 0;
        for (int count : need) {
            if (count > 0) {
                required++;
            }
        }

        int left = 0, right = 0;
        int match = 0; // 已匹配的字符种类数
        int start = 0, minLen = Integer.MAX_VALUE;

        while (right < s.length()) {
            // 右指针扩张
            char c = s.charAt(right);
            window[c]++;
            if (need[c] > 0 && window[c] == need[c]) {
                match++;
            }
            right++;

            // 当所有字符种类都匹配时,收缩左指针
            while (match == required) {
                // 更新最小窗口
                if (right - left < minLen) {
                    minLen = right - left;
                    start = left;
                }

                // 左指针收缩
                char d = s.charAt(left);
                if (need[d] > 0 && window[d] == need[d]) {
                    match--;
                }
                window[d]--;
                left++;
            }
        }

        return minLen == Integer.MAX_VALUE ? "" : s.substring(start, start + minLen);
    }
}

代码详解

1. 数组优化说明

  • 由于字符仅为英文字母(ASCII 128 个),用 int[128] 数组代替 HashMap,时间复杂度更优,空间复杂度为 O(1)。
  • need[]:t 中每个字符需要出现多少次
  • window[]:当前窗口内每个字符出现了多少次
  • required:t 中一共有多少种不同字符(必须全部匹配才算合法)
  • match:当前窗口内,次数已经达标的字符种类数
  • left / right:滑动窗口双指针
  • start / minLen:记录最小窗口的起点和长度

2. 核心变量 match

match 表示当前窗口内,次数完全满足 t 要求的字符种类数。

  • 当 match 等于 t 中不同字符的种类数时,窗口合法。
  • 每次右指针加入字符时,若该字符次数刚好达标,match++;
  • 每次左指针移除字符时,若该字符次数不再达标,match--。

3. 核心逻辑

  • 右指针一直往右走,把字符加入窗口
  • 加入后如果某个字符次数刚好达标 → match++
  • 当 match == required → 窗口完全合法
  • 进入内层 while,拼命收缩左指针,尝试找到更小窗口
  • 收缩时如果某个字符次数不达标了 → match--,退出循环
  • 最终记录最小窗口 → 截取返回

示例 1

模拟:s = "ADOBECODEBANC", t = "ABC"

  • need 数组:A:1, B:1, C:1,其他为 0。
  • 右指针扩张到 right=5(字符 C)时,match=3,窗口合法,开始收缩左指针,得到第一个合法窗口 "ADOBEC",长度 6。
  • 右指针继续扩张,直到 right=10(字符 A),match=3,收缩左指针,得到最小窗口 "BANC",长度 4。
  • 最终返回 "BANC",与示例结果一致。

复杂度分析

指标 复杂度 说明
时间复杂度 O(m+n) 右指针遍历 s 一次(O(m)),左指针最多遍历 s 一次(O(m)),统计 t 一次(O(n)),总复杂度线性
空间复杂度 O(1) 数组大小固定为 128,与输入规模无关

高频易错点总结

  1. 边界处理 :t 长度大于 s 时,直接返回空字符串。
  2. match 统计错误 :必须保证字符次数刚好等于 目标次数时才 match++,移除时次数等于 目标次数时才 match--。
  3. 窗口长度计算 :right - left(因为 right 已经右移一位,无需 +1)。
  4. 数组初始化 :need 和 window 数组初始化为 0,无需手动填充。
  5. 最终返回 :必须判断 minLen 是否更新,未更新则返回空字符串。

总结

这道题是滑动窗口的经典模板题 ,核心思想是「右指针扩张找合法窗口,左指针收缩找最小窗口」,用 match 变量快速判断窗口合法性,完美实现了 O(m+n) 的线性时间复杂度,完全满足进阶要求。

  • 面试优先级:这是面试中滑动窗口的必背题目,代码结构清晰,逻辑严谨,是考察字符串处理能力的标杆题。
  • 拓展应用:该模板可以直接套用在「最小覆盖子串」「字符串排列」「找到所有字母异位词」等同类滑动窗口题目中。

今天的每日算法练习就到这里,我们明天再见!👋

相关推荐
有毒的教程10 小时前
Dev-C++ 避免爆栈(Stack Overflow)完整方案
c++·算法·深度优先
在所不辞兄11 小时前
刚性问题误差容限设置技巧
深度学习·神经网络·算法·机器学习
啊啊啊啊啊!!!!11 小时前
unordered_map和unordered_set的使用
算法
java_nnnn11 小时前
LeetCode刷题双指针(快乐数+盛最多水的容器)Java
算法·leetcode·职场和发展
yi01119 小时前
LeetCode 219:存在重复元素 II——哈希表记录“最近一次出现的位置”
数据结构·人工智能·笔记·python·算法·leetcode·哈希表
YangYang9YangYan20 小时前
2027 秋招|数据科学与大数据技术面试项目价值回答思路,告别纯代码描述
大数据·面试·职场和发展
灯澜忆梦1 天前
【面向对象编程C++】| 基础语法
java·c++·算法
小蒋观天下1 天前
专项方案:大场景港口AI安防、多干扰环境下的算法调优与落地实操
人工智能·深度学习·算法·安全·机器学习·计算机视觉·ai大模型
Evand J1 天前
【电机滤波例程5】渐消因子自适应扩展卡尔曼滤波(EKF),MATLAB。PMSM负载突变下的状态估计。附下载链接
算法·matlab
H.莓飛1 天前
【数据结构】二叉树_OJ题
linux·开发语言·数据结构·算法