最小覆盖子串:解题思路与代码分析
问题描述
在给定的字符串 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
所有字符的最小子串。该方法高效、简洁,且能够处理大规模输入。
这种解法的关键点是巧妙利用哈希表记录字符频率,结合双指针来维护窗口,从而有效地找到符合条件的最小子串。