题目
给你一个字符串 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
由英文字母组成
思路
由题可知,实际是在寻找 包含t的 最短子字符串 那么就需要考虑: Q1:如何达成包含? A1:选择哈希表解决字母和个数问题 Q2:怎样寻找最短? A2:使用滑动窗口框住符合要求的子字符串 再缩小窗口寻找合适左边界,得到符合题意窗口大小 依次移动,直至滑动窗口结束指针到达字符串s末尾
解题过程
1.准备工作
创建两个哈希表 1.needT用于统计字符串t中的字母及其个数 (统计完就可以不管 字符串t 了,使命完成!我们去运用获得到的 统计数据needT 就OK) 2.haveS用于记录滑动窗口框住的字母及其个数
设置两个指针 1.起始指针i 2.终止指针j 滑动窗口需要先移动终止指针到第一个符合题意的位置 再移动起始指针,缩小窗口、 再增设一个int型变量cnt 用于统计符合要求的字母个数,可以理解为收集进度 还需要一个 string型变量ans 存储最后的最小覆盖字串 返回
2.逻辑代码
for循环开始 终止指针j向后移动 记录走过的每一个s[j],存至haveS中 如果 s[j] 的出现次数(即存入haveS中的s[j]对应个数)小于等于 needT[s[j]](满足题目需要的该字母个数) 收集进度cnt +1 当收集到的字母个数大于需要的 开始缩小滑动窗口 终止指针j 不变,向后移动 起始指针i 同时在 haveS 中去掉 i指针 所对应的 字母s[i]
当收集进度达到目标即 字符串t的长度(t限时返厂了哈) 动手比较得到最短窗口 这里还需要考虑没有匹配到合适子字符串的情况(即窗口为空) 取最小值更新 用字符串的substr方法切割最短的字串 最后返回该字串
3.复杂度
- 时间复杂度: O(n)
4.Code
class Solution {
public:
string minWindow(string s, string t) {
unordered_map<char,int>needT,haveS;
string ans;
int cnt=0;
// 统计 t 中每个字符的出现次数
for (char c : t) {// 遍历字符串 t
needT[c]++;// 增加对应字符的值
}
//滑动窗口!!!
for(int j = 0,i = 0; j < s.size();j++){
haveS[s[j]]++;//终止指针后移,读取s存入
// 如果 s[j] 的出现次数小于等于 needT[s[j]],收集进度 +1
if(haveS[s[j]] <= needT[s[j]]){//还没收集齐
cnt++;//收集进度+1
}
//缩小窗口左边界
while(haveS[s[i]] > needT[s[i]]){
//在初始位置的这个字母的个数 拥有大于需要
haveS[s[i]]--;//去掉
i++;//起始指针向后移动
}
//收集完毕
if (cnt == t.size()) {
if (ans.empty() || j - i + 1 < ans.size()){
//如果窗口为空,或当前窗口长度小于现存,更新
ans = s.substr(i, j - i + 1);
}
}
}
return ans;//返回最小字符串
}
};