题目链接:**. - 力扣(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
由英文字母组成
进阶:你能设计一个在 o(m+n)
时间内解决此问题的算法吗?
解法(滑动窗口+哈希表):
算法思路:
✸ 研究对象是连续的区间,因此可以尝试使用滑动窗口的思想来解决。
✸ 如何判断当前窗口内的所有字符是符合要求的呢?
·我们可以使用两个哈希表,其中一个将目标串的信息统计起来,另一个哈希表动态的维护窗口内字符串的信息。
·当动态哈希表中包含目标串中所有的字符,并且对应的个数都不小于目标串的哈希表中各个字符的个数,那么当前的窗口就是一种可行的方案。
算法流程:
✸ 定义两个全局的哈希表:1
号哈希表 hash1
用来记录子串的信息,2
号哈希表 hash2
用来记录目标串 t
的信息;
✸ 实现一个接口函数,判断当前窗口是否满足要求:
1️⃣ 遍历两个哈希表中对应位置的元素:
^ 如果 t
中某个字符的数量大于窗口中字符的数量,也就是 2
号哈希表某个位置大于 1
号哈希表。说明不匹配,返回 false
;
^ 如果全部匹配,返回 true
主函数中:
✸ 先将t
的信息放入2
号哈希表中;
✸ 初始化一些变量,左右指针:left = 0,right = 0
;目标子串的长度:len = INT_MAX
;目标子串的起始位置:retleft
;(通过目标子串的起始位置和长度,我们就能找到结果)
✸ 当right
小于字符串s
的长度时,一直下列循环:
1️⃣ 将当前遍历的元素扔进1
号哈希表中;
2️⃣ 检测当前窗口是否满足条件:
● 如果满足条件:
◦ 判断当前窗口是否变小。如果变小:更新长度 len
,以及字符串的起始位置retleft
;
◦ 判断完毕后,将左侧元素滑出窗口,顺便更新1
号哈希表;
◦ 重复上面两个过程,直到窗口不满足条件;
3️⃣ right++
,遍历下一个元素;
✸ 判断len
的长度是否等于INT_MAX
;
1️⃣ 如果相等,说明没有匹配,返回空串;
2️⃣ 如果不相等,说明匹配,返回s
中从 retleft
位置往后len
长度的字符串。
class Solution {
public:
string minWindow(string s, string t) {
int hash1[128] = {0};//统计字符串 t 中每个字符频次
int hash2[128] = {0};//统计窗口中每个字符频次
int kinds = 0;//统计有效字符有多少种
for(auto ch : t) {
if(hash1[ch]++ == 0) kinds++;
}
int MinLen = INT_MAX,begin = -1;
for(int left = 0, right = 0,count = 0; right < s.size(); right++){
char in = s[right];
//进窗口+维护
if(++hash2[in] == hash1[in]) count++;
while(count == kinds){//判断
if(right - left + 1 < MinLen){//更新结果
MinLen = right - left + 1;
begin = left;
}
//出窗口
char out = s[left++];
//维护
if(hash2[out]-- == hash1[out]) count--;
}
}
if(begin == -1) return "";
else return s.substr(begin,MinLen);
}
};