76. 最小覆盖子串 - 力扣(LeetCode)
一、题目要求
给你一个字符串 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
由英文字母组成
二、解法1-暴力破解 O(N^2) 会超时
class Solution {
public:
string minWindow(string s, string t) {
set<pair<int, string>> set; // 存储所有覆盖到的字符串
vector<int> tab(123-65, 0); // 需要的字符种类和数量
int differ = t.size(); // 标识是否都覆盖到了
for (const auto e : t) {
tab[e - 'A']--;
}
for (int i = 0; i < s.size(); i++) {
if (tab[s[i] - 'A'] < 0) { // 最短的字串的首个字母肯定是在t中也有的
string ret;
int tmp_differ = differ;
vector<int> tmp_tab(tab);
for (int j = i; j < s.size(); j++) {
ret.push_back(s[j]);
if (tmp_tab[s[j] - 'A'] < 0) {
tmp_tab[s[j] - 'A']++;
tmp_differ--;
if (tmp_differ == 0) { // 全部覆盖到了
set.insert({ret.size(), ret});
break;
}
}
}
}
}
if (set.empty())
return string();
return set.begin()->second;
}
};
三、解法2-哈希表+滑动窗口 O(N) 执行时间少,内存消耗高
用一个哈希表存储需要的字符,differ维护当前窗口缺少的字符的种类与数量
(1)若窗口不满足要求,就让窗口的右边界右移,吸收更多字符,直到满足要求为止,然后执行(2)。
(2)若窗口满足要求,就让窗口的左边界右移,逐个减少不需要的字符,当将要减少的字符会让窗口不满足条件时,就计算当前长度(可能要更新字串),减少这个字符后窗口就不满足要求了,又执行(1)。
class Solution {
public:
string minWindow(string s, string t) {
int differ = t.size();
unordered_map<char, int> map; // 需要的字符种类和数量
for (const auto c : t) {
map[c]--;
}
for (int i = 0; i < t.size(); i++) {
auto it = map.find(s[i]);
if (it != map.end()) {
if (it->second < 0)
differ--;
map[s[i]]++;
}
}
if(differ == 0) // 如果已经符合条件了,它的大小正好是t的大小,一定是最小的
return s.substr(0,t.size());
string ret;
int j = t.size(); // 将进入到值
int i = 0; // 将去掉的值
int min = -1; // 判断目前的字符串是不是最短的
while(i < s.size())
{
// differ==0,i向右走,否则j向右走
if(differ == 0)
{
auto it = map.find(s[i]);
if(it!=map.end()) // s[i]是map中有的字符
{
if(it->second > 0) // 有多余的s[i],differ不需要增加
{
(it->second)--;
}
else // 没有多余的s[i],differ需要增加,记录当前的长度
{
differ++;
(it->second)--;
if(min==-1 || j-i<min) // 获得第一个合法长度或者小于最小长度
{
min = j-i;
ret = s.substr(i,min); // 更新最小子串
}
}
}
i++; // i向右走
}
else // 窗口不符合,j向右走
{
auto it = map.find(s[j]);
if(it!=map.end()) // s[j]是map中有的字符
{
if((it->second) < 0) // 正好缺少s[j],differ才减少
{
differ--;
}
(it->second)++;
}
j++; // j向右走
}
if(j >= s.size() && differ != 0) // 此时无论i如何移动,都不可能符合条件了
{
break;
}
}
return ret;
}
};