最小覆盖子串:解题思路与代码分析
问题描述
在给定的字符串 s 中,找到包含字符串 t 所有字符的最小子串。如果 s 中没有这样的子串,则返回空字符串 ""。
示例:
-
输入:
s = "ADOBECODEBANC",t = "ABC"
输出:"BANC"
解释:最小覆盖子串"BANC"包含来自字符串t的'A'、'B'和'C'。 -
输入:
s = "a",t = "a"
输出:"a"
解释:整个字符串s是最小覆盖子串。 -
输入:
s = "a",t = "aa"
输出:""
解释:t中的字符'a'有两个,而s中只有一个字符'a',因此没有符合条件的子串,返回空字符串。
题目要求:
- 对于
t中重复的字符,最小覆盖子串中该字符数量必须不少于t中该字符的数量。 - 最优解的时间复杂度应为
O(m+n),其中m是s的长度,n是t的长度。
解题思路
此问题可以通过 滑动窗口 技巧来有效解决,使用双指针和哈希表来判断窗口是否包含所有字符。
滑动窗口算法
滑动窗口是一种常用于解决此类“子串”问题的技巧。基本思路是:
- 使用两个指针
left和right,分别表示当前窗口的左右边界。 - 通过右指针不断扩展窗口,直到窗口包含了所有
t中的字符。 - 当窗口有效时,尝试通过左指针收缩窗口,来获取最小的覆盖子串。
为了跟踪窗口中的字符,我们使用两个哈希表:
count_t:存储字符串t中字符的频率。count_s:存储当前窗口中字符的频率。
关键点:
- 窗口中的字符需要包含
t中所有字符,并且字符的数量要满足t中对应字符的频率。 - 当窗口满足条件时,尝试收缩窗口,更新最小覆盖子串。
代码实现
from collections import Counterclass Solution:def minWindow(self, s: str, t: str) -> str:ans_left, ans_right = -1, len(s)left = 0count_s = Counter() # 当前窗口中的字符频率count_t = Counter(t) # 目标字符串 t 中的字符频率# 记录 t 中需要的字符数量required = len(count_t)formed = 0 # 当前窗口满足的字符种类数# 滑动窗口遍历for right, x in enumerate(s):# 将右边字符加入窗口count_s[x] += 1# 如果窗口中的字符数量与 t 中的字符数量匹配,更新 formedif count_s[x] == count_t[x]:formed += 1# 当窗口包含所有 t 中的字符时,尝试收缩左边界while formed == required:if right - left < ans_right - ans_left:ans_left, ans_right = left, right# 收缩窗口,减小窗口左边界count_s[s[left]] -= 1if count_s[s[left]] < count_t[s[left]]:formed -= 1left += 1# 如果找到了符合条件的子串,返回它;否则返回空字符串return "" if ans_left == -1 else s[ans_left:ans_right + 1]
代码解析
-
变量初始化:
ans_left和ans_right用来记录最小覆盖子串的左右边界。count_s用于存储当前窗口中字符的频率。count_t用于存储字符串t中字符的频率。required记录t中不同字符的数量。formed记录当前窗口中满足t中字符频率要求的字符种类数。
-
遍历字符串
s:- 对于每个字符
x,增加count_s[x]。 - 如果
count_s[x]达到count_t[x],表示窗口中字符x的数量满足要求,formed增加。
- 对于每个字符
-
窗口收缩:
- 当
formed == required时,表示当前窗口已经包含了t的所有字符,可以尝试通过左指针收缩窗口。 - 在收缩过程中,更新最小覆盖子串的边界,并根据
count_s[s[left]]是否小于count_t[s[left]]来判断是否减少formed。
- 当
-
返回结果:
- 如果找到满足条件的子串,返回其子串;否则返回空字符串。
时间复杂度分析
- 每个字符至多被访问两次(一次是右指针扩展,另一次是左指针收缩),所以时间复杂度是
O(m+n),其中m是s的长度,n是t的长度。 - 因为每个字符的计数操作是常数时间,哈希表的查找和更新操作也是常数时间,所以算法是高效的。
总结
通过滑动窗口技术,我们能够在 O(m+n) 的时间复杂度内找到字符串 s 中涵盖 t 所有字符的最小子串。该方法高效、简洁,且能够处理大规模输入。
这种解法的关键点是巧妙利用哈希表记录字符频率,结合双指针来维护窗口,从而有效地找到符合条件的最小子串。
