LeetCode-76.最小覆盖子串
1、题目描述:
给你一个字符串 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 的子串中, 因此没有符合条件的子字符串,返回空字符串。
提示:
m == s.length
n == t.length
1 <= m, n <= 105
s
和t
由英文字母组成
2、代码:
class Solution {
public:
string minWindow(string s, string t) {
// 如果字符串 s 或 t 为空,或者 s 的长度小于 t 的长度,直接返回空字符串
if (s.empty() || t.empty() || s.size() < t.size()) {
return "";
}
// need 哈希表:记录字符串 t 中每个字符的需求个数
unordered_map<char, int> need;
// window 哈希表:记录当前窗口中每个字符的数量
unordered_map<char, int> window;
// 初始化 need 哈希表,统计 t 中每个字符的出现次数
for (char c : t) {
++need[c];
}
// 定义滑动窗口的左右边界
int left = 0, right = 0;
// 记录最小覆盖子串的起始位置和长度
int start = 0, len = INT_MAX;
// valid 变量:表示窗口中满足 need 条件的字符个数
int valid = 0;
// 开始滑动窗口
while (right < s.size()) {
// c 是将要移入窗口的字符
// 右边界向右扩展
char c = s[right++];
// 更新窗口内的数据
if (need.count(c)) { // 如果字符 c 在 need 中(即它是 t 中的字符)
++window[c]; // 将 c 加入窗口,并更新其计数
if (window[c] == need[c]) { // 如果窗口中 c 的数量达到了 need 中的需求
++valid; // 满足条件的字符个数加一
}
}
// 判断左侧窗口是否需要收缩
while (valid == need.size()) { // 当窗口中的字符已经满足了 t 中所有字符的需求时
// 更新最小覆盖子串
if (right - left < len) { // 如果当前窗口比之前记录的最小窗口更小
start = left; // 更新最小窗口的起始位置
len = right - left; // 更新最小窗口的长度
}
// d 是将要移出窗口的字符
// 左边界向右收缩
char d = s[left++];
// 更新窗口内的数据
if (need.count(d)) { // 如果字符 d 在 need 中
if (window[d] == need[d]) {
// 如果窗口中 d 的数量刚好等于 need 中的需求
--valid; // 满足条件的字符个数减一
}
--window[d]; // 将 d 移出窗口,并更新其计数
}
}
}
// 返回结果
// 如果 len 仍然是 INT_MAX,说明没有找到符合条件的子串,返回空字符串
// 否则,返回从 start 开始长度为 len 的子串
return len == INT_MAX ? "" : s.substr(start, len);
}
};
3、解题思路:
1. 这是一个滑动窗口问题,需要 left 和 right 指针的滑动去遍历 s 字符串,首先创建2个无序的map(相当于哈希表,访问效率更高),一个need用来存储需要包含的字符种类及其数量(也就是 t 字符串),一个window用来存储 left 和 right 指针截取的 s 字符串中,所包含的字符种类及其数量。
2. 开始循环,循环条件是 right < s.size(),每次循环之前,都会进行 char c = s[right++] 操作,也就是提取 right 当前指向的 s 字符串的字符,并且 right++。
3. 开始扩充window,当 need 里面有 c 时,++window[c] ,如果 need[c] == window[c] 时,代表window 已经包含 need 里面的所有 c 字符了,那么 ++valid,也就是说,某个字符,已经完全满足。
4. 扩从不一定结束,但可以尝试开始收缩window,当valid == need.size()时,开始循环,收缩窗口,当right - left < len 时(哈希表是从 0 开始的,不需要 right - left + 1),更新start 、len的值,然后 char d = s [left++],当 need.count(d) 时,--window[d],当 window[d] == need[d]时,也就是不需要再收缩了,--valid,退出循环