题目
给你一个字符串 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 的子串中, 因此没有符合条件的子字符串,返回空字符串。
分析
滑动窗口法
使用 unordered_map
类型的 target
统计字符串 t
中每个字符的出现次数。定义 window
用于记录窗口中每个字符的出现次数,valid
用于记录窗口中已经满足条件的字符种类数。
初始化左右指针 left
和 right
都为 0,start
用于记录最小覆盖子串的起始位置,len
用于记录最小覆盖子串的长度,初始化为 INT_MAX
。
右指针 right
不断向右移动,将字符移入窗口,并更新 window
中该字符的出现次数。如果该字符在 target
中,并且 window
中该字符的出现次数等于 target
中该字符的出现次数,则 valid
加 1。
当 valid
等于 target
的大小,说明窗口中已经涵盖了 t
中所有字符,此时开始收缩左侧窗口。
在收缩左侧窗口时,更新最小覆盖子串的起始位置和长度。同时,将左侧字符移出窗口,并更新 window
中该字符的出现次数。如果该字符在 target
中,并且 window
中该字符的出现次数小于 target
中该字符的出现次数,则 valid
减 1。
时间复杂度:O(),
是字符串
s
的长度
空间复杂度:O(),
是字符串
t
中不同字符的个数
class Solution {
public:
string minWindow(string s, string t) {
if (s.empty() || t.empty() || s.length() < t.length()) {
return "";
}
// 统计 t 中每个字符的出现次数
unordered_map<char, int> target;
for (char c : t) {
target[c]++;
}
// 窗口中字符的出现次数
unordered_map<char, int> window;
// 窗口中已经满足条件的字符种类数
int valid = 0;
// 左右指针
int left = 0, right = 0;
// 最小覆盖子串的起始位置和长度
int start = 0, len = INT_MAX;
while (right < s.length()) {
// 移入窗口的字符
char c = s[right];
right++;
// 更新窗口内的数据
if (target.count(c)) {
window[c]++;
if (window[c] == target[c]) {
valid++;
}
}
// 判断左侧窗口是否要收缩
while (valid == target.size()) {
// 更新最小覆盖子串
if (right - left < len) {
start = left;
len = right - left;
}
// 移出窗口的字符
char d = s[left];
left++;
// 更新窗口内的数据
if (target.count(d)) {
if (window[d] == target[d]) {
valid--;
}
window[d]--;
}
}
}
return len == INT_MAX ? "" : s.substr(start, len);
}
};