BM90 最小覆盖子串
知识点哈希双指针字符串
描述
给出两个字符串 s 和 t,要求在 s 中找出最短的包含 t 中所有字符的连续子串。
数据范围:,保证s和t字符串中仅包含大小写英文字母要求: 时间复杂度
例如:
找出的最短子串为.
注意:
如果 s 中没有包含 t 中所有字符的子串,返回空字符串 “”;
满足条件的子串可能有很多,但是题目保证满足条件的最短的子串唯一。
示例1
输入:
"XDOYEZODEYXNZ","XYZ"
复制返回值:
"YXNZ"
复制
示例2
输入:
"abcAbA","AA"
复制返回值:
"AbA"
题解
思路:
- 假设要在字符串s中找字符串t,使用一个hash表n存放t每个字符出现的次数
- 遍历整个字符串s,使用hash表m记录某个区间中在字符串t中的字符出现的次数
- 使用一个计数器count标记在字符串s的某个区间中出现字符的种类数,对于在t中的某个字符x,只有大于m[x] >= n[x]的时候才计数一次,且在该区间只记一次,因此使用一个辅助hash表b来左标记
- 当索引到达i的时候,如果s[i]在n中,则对m[s[i]]递增,如果m[s[i]]的值大于等于n[s[i]]则可以对count累加,同时设置b[s[i]] = false
- 如果count == n.size(),那么表示所有的条件都满足了,让缩小左边界的同时对m[s[i]]递减,直到count < n.size(),此时左右边界是在该区间的最小子串长度
- 遍历完整个数组,当长度变小的时候,更新长度
注意:
实际上,可以讲代码中的m和n合并使用同一个hash表,前面初始化n的时候初始化为负值,后面统计key的时候将对应的hash值递增,然后边界条件就是和0比较了。
代码如下:
std::string minWindow(std::string S, std::string T)
{
int left = 0;
size_t len = S.npos;
int ans_start = 0;
std::unordered_map<char, int> m;
std::unordered_map<char, int> n;
std::unordered_map<char, bool> b;
int count = 0;
for (int i = 0; i < T.size(); ++i)
{
n[T[i]]++;
b[T[i]] = false;
}
for (int i = 0; i < S.size(); ++i)
{
auto x = S[i];
if (n.find(x) == n.end())
{
continue;
}
m[x]++;
if (m[x] >= n[x] && !b[x])
{
count++;
b[x] = true;
}
if (count == n.size())
{
int right = i;
// 缩小左边界,直到某个key出现的次数刚好为要求的最小次数
while (left < right)
{
if (m.find(S[left]) != m.end())
{
m[S[left]]--;
if (m[S[left]] >= n[S[left]])
{
left++;
continue;
}
else
{
count--;
b[S[left]] = false;
break;
}
}
left++;
}
// 这部分注释是代码可以省略,因为count == n.size()一定是s[i]对某个key累加后刚好满足的
// 去掉s[i]则count < n.size()
// while (right > left)
// {
// if (m.find(S[right]) != m.end())
// {
// if (m[S[right]] > n[S[right]])
// {
// m[S[right]]--;
// count--;
// right--;
// continue;
// }
// else
// {
// b[S[right]] = false;
// break;
// }
// }
// right--;
// }
if (right - left + 1 < len)
{
ans_start = left;
len = right - left + 1;
}
left++;
}
}
if (len == S.npos)
{
return "";
}
return S.substr(ans_start, len);
}