news 2026/4/16 9:09:44

【Hot100|13-LeetCode76最小覆盖子串】

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Hot100|13-LeetCode76最小覆盖子串】

一、问题理解

问题描述

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

注意

  • 对于t中重复字符,子串中该字符数量必须不少于t中该字符数量。

  • 如果存在这样的子串,题目保证它是唯一的答案。

示例

text

输入:s = "ADOBECODEBANC", t = "ABC" 输出:"BANC" 解释:最小子串 "BANC" 包含 'A'、'B' 和 'C' 输入:s = "a", t = "a" 输出:"a" 输入:s = "a", t = "aa" 输出:"" 解释:t 中有两个 'a',但 s 中只有一个 'a'

二、核心思路:滑动窗口 + 字符计数

暴力解法的局限性

枚举s中所有子串并检查是否包含t的所有字符,时间复杂度为 O(n³),效率极低。

滑动窗口优化思路

  1. 字符频率统计:用哈希表(Counter)记录t中每个字符的出现次数。

  2. 滑动窗口维护:用左右指针leftright表示当前窗口。

  3. 扩展窗口right指针向右移动,扩大窗口,直到窗口包含t的所有字符。

  4. 收缩窗口:当窗口包含t的所有字符时,移动left指针,缩小窗口,寻找最小子串。

  5. 更新结果:记录满足条件的最小窗口。

三、代码逐行解析

python

from collections import Counter from typing import List class Solution: def minWindow(self, s: str, t: str) -> str: # 1. 初始化计数器 # cnt_s: 记录当前窗口中字符的出现次数 # cnt_t: 记录t中字符的出现次数 cnt_s = Counter() cnt_t = Counter(t) # 2. 初始化结果边界 # ans_left, ans_right 记录最小窗口的边界 # 初始设置为不可能的值,便于后续更新 ans_left, ans_right = -1, len(s) # 3. 滑动窗口左指针 left = 0 # 4. 遍历字符串s,right是窗口右边界 for right, c in enumerate(s): # 4.1 将当前字符加入窗口计数器 cnt_s[c] += 1 # 4.2 当窗口包含t的所有字符时,尝试收缩窗口 while cnt_s >= cnt_t: # 4.2.1 如果当前窗口更小,更新结果 if right - left < ans_right - ans_left: ans_left, ans_right = left, right # 4.2.2 将左边界字符移出窗口 cnt_s[s[left]] -= 1 # 如果字符计数为0,从计数器中删除 if cnt_s[s[left]] == 0: del cnt_s[s[left]] # 4.2.3 左指针右移,收缩窗口 left += 1 # 5. 返回结果 # 如果ans_left仍为-1,说明没有找到符合条件的窗口 return "" if ans_left < 0 else s[ans_left:ans_right + 1]

四、关键步骤详解

1. 字符计数初始化

python

cnt_s = Counter() # 窗口字符计数器 cnt_t = Counter(t) # t的字符计数器
  • cnt_t记录了t中每个字符需要出现的次数

  • cnt_s动态记录当前窗口中每个字符的出现次数

2. 滑动窗口维护

python

for right, c in enumerate(s): cnt_s[c] += 1 # 扩展窗口 while cnt_s >= cnt_t: # 当窗口包含t的所有字符时 # 更新最小窗口 if right - left < ans_right - ans_left: ans_left, ans_right = left, right # 收缩窗口 cnt_s[s[left]] -= 1 if cnt_s[s[left]] == 0: del cnt_s[s[left]] left += 1

关键点

  • cnt_s >= cnt_t:这是Python Counter的一个特性,表示对于cnt_t中的每个键,cnt_s中对应的值都大于等于它

  • 当窗口满足条件时,我们尝试收缩窗口以找到更小的满足条件的窗口

3. 边界条件处理

python

return "" if ans_left < 0 else s[ans_left:ans_right + 1]
  • 如果ans_left仍然是初始值 -1,说明没有找到符合条件的窗口,返回空字符串

  • 否则返回最小窗口子串

五、优化版本(使用数组代替Counter)

对于ASCII字符,我们可以使用固定大小的数组来提高效率:

python

class Solution: def minWindow(self, s: str, t: str) -> str: # 使用数组记录字符计数(ASCII字符共128个) need = [0] * 128 # t中字符需要的数量 window = [0] * 128 # 窗口中字符的数量 # 统计t中字符 for ch in t: need[ord(ch)] += 1 # 初始化 left, right = 0, 0 min_len = float('inf') min_left = 0 # 记录需要匹配的字符种类数 need_count = len(t) match_count = 0 # 滑动窗口 while right < len(s): # 当前字符 ch = s[right] right += 1 # 如果当前字符是t中需要的字符 if need[ord(ch)] > 0: window[ord(ch)] += 1 # 如果窗口中的该字符数量不超过需要的数量,匹配数加1 if window[ord(ch)] <= need[ord(ch)]: match_count += 1 # 当所有字符都匹配时,尝试收缩窗口 while match_count == need_count: # 更新最小窗口 if right - left < min_len: min_len = right - left min_left = left # 左边界字符 left_ch = s[left] left += 1 # 如果左边界字符是t中需要的字符 if need[ord(left_ch)] > 0: # 减少窗口中该字符的计数 window[ord(left_ch)] -= 1 # 如果窗口中的该字符数量少于需要的数量,匹配数减1 if window[ord(left_ch)] < need[ord(left_ch)]: match_count -= 1 # 返回结果 return "" if min_len == float('inf') else s[min_left:min_left + min_len]

六、复杂度分析

时间复杂度:O(n + m)

  • n是字符串s的长度,m是字符串t的长度

  • 每个字符最多被访问两次(一次由右指针,一次由左指针)

  • 总体时间复杂度为 O(n)

空间复杂度:O(1)

  • 使用固定大小的数组(128个元素)或有限的Counter

  • 空间消耗与输入字符串长度无关

七、实例演示

s = "ADOBECODEBANC",t = "ABC"为例,演示滑动窗口过程:

  1. 初始状态

    • cnt_t = {'A':1, 'B':1, 'C':1}

    • left = 0,right = 0

    • 窗口为空,不满足条件

  2. 扩展窗口

    • right移动到 5,窗口为 "ADOBEC",包含 A、B、C,满足条件

    • 开始收缩窗口

  3. 收缩窗口

    • 记录当前窗口 "ADOBEC" (长度 6)

    • 移动left到 1,窗口为 "DOBEC",不满足条件(缺少 A)

    • 停止收缩

  4. 继续扩展

    • right移动到 10,窗口为 "DOBECODEBA",包含 A、B,缺少 C

    • right移动到 12,窗口为 "DOBECODEBANC",包含 A、B、C,满足条件

    • 开始收缩窗口

  5. 再次收缩

    • 记录当前窗口 "DOBECODEBANC" (长度 12)

    • 移动left,直到窗口为 "BANC" (长度 4)

    • 此时窗口满足条件且是最小窗口

  6. 最终结果

    • 最小窗口为 "BANC"

八、常见问题与解答

Q1: 为什么使用while cnt_s >= cnt_t而不是if

  • 使用while循环可以确保在窗口满足条件时,我们不断尝试收缩窗口以找到更小的满足条件的窗口

  • 使用if只会收缩一次,可能错过更小的窗口

Q2: 如何处理t中的重复字符?

  • cnt_t记录了每个字符需要的数量

  • cnt_s >= cnt_t确保了窗口中每个字符的数量不少于t中该字符的数量

Q3: 如果t中有字符不在s中怎么办?

  • 算法会遍历整个s,如果找不到满足条件的窗口,最终返回空字符串

Q4: 为什么需要删除计数为0的字符?

  • cnt_s[s[left]]减到0时,如果不删除,cnt_s >= cnt_t的比较可能会出错

  • 例如,如果t中没有某个字符,但cnt_s中有这个字符且计数为0,比较时这个字符不会影响结果

九、相关题目

1. LeetCode 567. 字符串的排列

python

def checkInclusion(s1: str, s2: str) -> bool: # 判断s2是否包含s1的排列 from collections import Counter cnt_s1 = Counter(s1) cnt_window = Counter() left = 0 for right in range(len(s2)): cnt_window[s2[right]] += 1 # 当窗口大小等于s1长度时 if right - left + 1 == len(s1): if cnt_window == cnt_s1: return True # 移动左指针 cnt_window[s2[left]] -= 1 if cnt_window[s2[left]] == 0: del cnt_window[s2[left]] left += 1 return False

2. LeetCode 438. 找到字符串中所有字母异位词

python

def findAnagrams(s: str, p: str) -> List[int]: # 找到s中所有p的字母异位词的起始索引 from collections import Counter cnt_p = Counter(p) cnt_window = Counter() result = [] left = 0 for right in range(len(s)): cnt_window[s[right]] += 1 # 当窗口大小等于p长度时 if right - left + 1 == len(p): if cnt_window == cnt_p: result.append(left) # 移动左指针 cnt_window[s[left]] -= 1 if cnt_window[s[left]] == 0: del cnt_window[s[left]] left += 1 return result

3. LeetCode 3. 无重复字符的最长子串

python

def lengthOfLongestSubstring(s: str) -> int: # 找到不含重复字符的最长子串长度 from collections import defaultdict char_index = {} max_len = 0 left = 0 for right in range(len(s)): if s[right] in char_index and char_index[s[right]] >= left: left = char_index[s[right]] + 1 char_index[s[right]] = right max_len = max(max_len, right - left + 1) return max_len

十、总结

核心要点

  1. 滑动窗口是关键:用左右指针维护当前窗口,动态调整窗口大小

  2. 字符计数是基础:用哈希表或数组记录字符出现次数,判断窗口是否满足条件

  3. 双指针移动策略

    • 右指针扩展窗口,直到窗口满足条件

    • 左指针收缩窗口,寻找最小满足条件的窗口

优化技巧

  1. 使用数组代替哈希表:对于固定字符集(如ASCII),使用数组可以提高效率

  2. 记录匹配字符数:使用match_count变量记录已匹配的字符数,避免每次比较整个哈希表

  3. 提前终止:如果找到长度为len(t)的窗口,可以直接返回,因为这是可能的最小窗口

适用场景

  • 需要在字符串中寻找满足特定条件的子串

  • 条件通常与字符出现次数有关

  • 要求时间复杂度为 O(n)

掌握滑动窗口+字符计数的解法,不仅能够解决最小覆盖子串问题,还能够解决一系列类似的字符串匹配问题,是面试中非常重要的算法技巧。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/15 14:42:40

python088酒店宾馆客房管理系统设计与实现vue3

目录 技术栈组成系统核心模块关键技术实现典型功能代码示例系统特色 开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01; 以下是关于Python酒店宾馆客房管理系统设计与实现&#xff08;Vue3前端…

作者头像 李华
网站建设 2026/4/14 10:55:39

【课程设计/毕业设计】基于springboot的线下演出售票管理系统基于SpringBoot+Vue的线下演出售票管理系统设计与实现【附源码、数据库、万字文档】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/4/12 22:00:22

python106-大学生校园线上招聘系统vue3

目录 技术栈与框架选择核心功能模块关键实现细节扩展性与优化 开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01; 技术栈与框架选择 Python 后端采用 Flask/Django 框架&#xff0c;提供 REST…

作者头像 李华
网站建设 2026/4/11 0:54:16

珲春本地人去的烤肉哪家好吃

珲春本地人私藏&#xff01;这家乳酸菌烤肉好吃到爆在珲春&#xff0c;烤肉是当地美食文化中不可或缺的一部分&#xff0c;很多人都热衷于探寻好吃的烤肉店。而延炭乳酸菌烤肉&#xff0c;就是一家深受珲春本地人喜爱的宝藏烤肉店。健康理念&#xff0c;独树一帜延炭乳酸菌烤肉…

作者头像 李华
网站建设 2026/4/8 15:04:44

Java计算机毕设之基于Java的演出购票系统基于springboot的线下演出售票管理系统(完整前后端代码+说明文档+LW,调试定制等)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华