📘题目描述
给你一个字符串 s
、一个字符串 t
。返回 s
中涵盖 t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 ""
。
注意:
- 对于
t
中重复字符,我们寻找的子字符串中该字符数量必须不少于t
中该字符数量。 - 如果
s
中存在这样的子串,我们保证它是唯一的答案。
示例 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 的子串中, 因此没有符合条件的子字符串,返回空字符串。
🧠解题思路:滑动窗口 + 哈希表计数
这道题是滑动窗口的高阶变体,核心在于如何快速验证窗口是否包含了 t 的所有字符(及其频次)。
🔍算法框架
-
维护两个哈希表:
-
cnt_t
:统计t
中每个字符的出现次数 -
cnt_s
:统计当前窗口内的字符出现次数
-
-
使用双指针
[left, right]
表示滑动窗口 -
每次扩展
right
,加入一个字符 -
一旦窗口中包含了
t
所有字符,尝试收缩窗口,更新最小长度答案 -
直到窗口不满足条件,再继续右扩
✅Python 实现
class Solution:
def minWindow(self, s: str, t: str) -> str:
ans_left, ans_right = -1, len(s)
cnt_s = Counter()
cnt_t = Counter(t)
left = 0
for right, c in enumerate(s):
cnt_s[c] += 1
while cnt_s >= cnt_t:
if right - left < ans_right - ans_left:
ans_left, ans_right = left, right
cnt_s[s[left]] -= 1
left += 1
return '' if ans_left < 0 else s[ans_left: ans_right + 1]
🔄思维图示
以 s = "ADOBECODEBANC"
,t = "ABC"
为例:
步骤 | 窗口 | 是否包含 A/B/C | 尝试缩小窗口 | 当前最小窗口 |
---|---|---|---|---|
A→D→O→B→E→C | [0:5] = "ADOBEC" | ✅ | 是 | "ADOBEC" |
收缩窗口 | → [3:5] = "BEC" | ✅ | 是 | ✅ "BEC" → "BANC" |
遇到更小覆盖窗口 | [9:12] = "BANC" | ✅ | 是 | ✅✅✅ "BANC" |
⏱️时间与空间复杂度分析
项目 | 复杂度 |
---|---|
时间复杂度 | O(n + m) |
空间复杂度 | O(n + m)(字符集大小,哈希表) |
❗常见易错点
错误点 | 原因说明 |
---|---|
哈希表频次判断 cnt_s >= cnt_t | Counter 在 Python 中可以直接比较子集关系(必须频次也满足) |
忽略重复字符 | 例如 t = "AABC" ,需要频次也匹配 |
忘记 ans_right + 1 截取字符串 | Python 字符串切片是半开区间 [left:right+1] |
✨总结
-
本题是滑动窗口的代表性“困难题”,核心在于窗口合法性判断。
-
Counter
的子集比较cnt_s >= cnt_t
非常简洁优雅。 -
尽管逻辑复杂,但实现中通过计数、扩展+收缩窗口构建思路清晰路径。
-
优化版本(减少
Counter
比较成本)。
📌如果你喜欢这道题解法,可以收藏并整理进你的滑动窗口技巧笔记中!