力扣打卡每日一题————最小覆盖子串

一、问题概述

给定两个字符串 st,在 s 中找出包含 t 所有字符的最小长度子串,若不存在则返回空字符串。

核心需求:① 子串必须包含 t 的全部字符(含重复字符);② 子串长度尽可能小;③ 时间复杂度需高效。

示例:输入 s="ADOBECODEBANC"t="ABC",输出 "BANC"

二、核心解法:滑动窗口

1. 解法思想

滑动窗口是一种基于双指针的高效策略,通过维护一个"动态窗口"在主串中移动,实现"扩张-收缩"的循环,从而在一次遍历中找到满足条件的最小窗口。核心逻辑分为两步:

  • 扩张右边界 :用右指针遍历主串,将字符纳入窗口,直到窗口包含 t 的所有字符(满足"有效性");

  • 收缩左边界:当窗口有效时,移动左指针缩小窗口范围,直到窗口不再满足条件,此过程中记录最小窗口的位置和长度。

本质:通过窗口的动态调整,避免暴力解法中"枚举所有子串"的冗余计算,将时间复杂度从 O(n²) 优化至 O(n)(n 为主串长度)。

2. 关键数据结构与变量

变量/结构 作用 示例(t="ABC")
哈希表 need 统计 t 中各字符的"需求数量"(即需要包含的次数) need = {'A':1, 'B':1, 'C':1}
左指针 left 窗口左边界,控制窗口收缩 初始为 0,随条件动态右移
右指针 right 窗口右边界,控制窗口扩张 从 0 遍历至 s 末尾
计数器 valid 记录窗口中"已满足需求"的字符种类数(当 valid = need.size() 时窗口有效) 窗口包含 A、B 时,valid=2;包含 A、B、C 时,valid=3(有效)
min_len 记录最小窗口的长度,初始为无穷大 找到有效窗口后更新,最终用于判断是否存在结果
start 记录最小窗口的起始索引,用于最终截取子串 窗口缩至最小时更新

3. 完整执行流程

(以 s="ADOBECODEBANC"、t="ABC" 为例)

  1. 初始化:need = {'A':1, 'B':1, 'C':1},left=0,valid=0,min_len=INT_MAX,start=0;

  2. 扩张右边界(right=0 至 4):窗口为 "ADOBE",包含 A、B,valid=2(未满足);

  3. 扩张至 right=5:窗口纳入 'C',need['C'] 变为 0,valid=3(满足条件);此时窗口为 "ADOBEC",长度 6,更新 min_len=6,start=0;

  4. 收缩左边界:左移 left 至 1(移出 'A'),need['A'] 变为 1,valid=2(不再满足);停止收缩,此时最小窗口仍为 "ADOBEC";

  5. 继续扩张与收缩:right 继续移动至 10(纳入 'B'、'A' 等),当窗口为 "CODEBA" 时再次满足条件,收缩左边界至 8,窗口变为 "BANC",长度 4,更新 min_len=4,start=8;

  6. 遍历结束:截取 s[8:12],得到结果 "BANC"。

cpp 复制代码
class Solution {
public:
    string minWindow(string s, string t) {
        // 特殊情况:t为空或s长度小于t,直接返回空
        if (t.empty() || s.length() < t.length()) return "";

        // 1. 初始化需求哈希表
        unordered_map<char, int> need;
        for (char c : t) need[c]++;
        
        // 2. 滑动窗口核心变量
        int left = 0, right = 0;
        int valid = 0;
        int min_len = INT_MAX;
        int start = 0;

        // 3. 扩张右边界
        while (right < s.length()) {
            char c = s[right];
            right++; // 右指针右移,纳入当前字符

            // 若当前字符是t需要的,更新需求统计
            if (need.count(c)) {
                need[c]--;
                // 该字符的需求刚好满足(从1→0),有效种类数+1
                if (need[c] == 0) valid++;
            }

            // 4. 窗口有效时,收缩左边界
            while (valid == need.size()) {
                // 更新最小窗口信息
                if (right - left < min_len) {
                    min_len = right - left;
                    start = left;
                }

                // 左指针右移,移出当前字符
                char d = s[left];
                left++;

                // 若移出的是t需要的字符,更新需求
                if (need.count(d)) {
                    // 该字符的需求从满足→不满足(从0→1),有效种类数-1
                    if (need[d] == 0) valid--;
                    need[d]++;
                }
            }
        }

        // 5. 结果判断:若min_len未更新,说明无有效窗口
        return min_len == INT_MAX ? "" : s.substr(start, min_len);
    }
};

四、注意事项

1. 哈希表的"需求计数"逻辑

need 表中字符的计数可能为负数,代表该字符在窗口中"超额出现"。例如 t="A",窗口中包含两个 'A',则 need['A'] = -1。仅当计数从 1 变为 0 时,valid 才加 1;仅当计数从 0 变为 1 时,valid 才减 1------避免因超额字符误判窗口有效性。

2. 特殊边界处理

  • t 为空字符串:按题目要求返回空(或根据场景定义);

  • s 长度小于 t:直接返回空,不可能包含 t 的所有字符;

  • 无有效窗口:min_len 始终为 INT_MAX,最终返回空。

3. 窗口收缩的终止条件

收缩左边界的循环条件是 valid == need.size()(窗口有效),一旦 valid 减小(窗口不再满足)则停止收缩,避免过度收缩导致遗漏后续更小窗口。

4. 最小窗口的记录时机

必须在收缩左边界的过程中记录最小窗口,因为此时窗口刚满足条件,收缩过程中才可能找到更小的有效窗口;若在扩张时记录,窗口未经过收缩,长度并非最优。

五、总结

通过双指针控制窗口的扩张与收缩,用哈希表和计数器判断窗口有效性,在一次遍历中完成最优解查找。

相关推荐
WJSKad123519 小时前
传送带物体检测识别_基于YOLO11与RGCSPELAN改进算法_工业视觉检测系统
人工智能·算法·视觉检测
仍然.20 小时前
JavaDataStructure---排序
数据结构·算法·排序算法
ZhuNian的学习乐园20 小时前
LLM知识检索增强:RAG_系统化解析与工程实践
人工智能·算法
WBluuue20 小时前
Codeforces Good Bye 2025 Div1+2(ABCDE)
c++·算法
骑自行车的码农20 小时前
🕹️ 设计一个 React 重试
前端·算法·react.js
代码游侠20 小时前
应用——MQTT客户端开发
服务器·c语言·开发语言·数据结构·算法
蓝天下的守望者20 小时前
由continue引发的一个debug灾难
算法·systemverilog
明洞日记20 小时前
【VTK手册034】 vtkGeometryFilter 深度解析:高性能几何提取与转换专家
c++·图像处理·算法·ai·vtk·图形渲染
额呃呃20 小时前
operator new/delete
开发语言·c++·算法
hweiyu0021 小时前
二分图匹配算法:匈牙利算法
算法