最小覆盖子串
今天做题的时候了解到了滑动窗口,主要是在数组上应用,它能把 O(n2) 的暴力解法优化到 O(n) ,在对数组的操作中非常好用
不了解滑动窗口的建议先去学一下再来解决这道题
题目描述
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
注意:
给你一个字符串 s 和一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 ""
注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。
示例:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
如果采用暴力解法的话
//伪代码
for(int i = 0; i < s.length(); i++) {
for(int j = 0; j < s.length(); j++) {
if(s[i~j] 包含 子串 t) {
minlen = minlen < i - j + 1 ? minlen : i - j + 1;
}
}
}
算法复杂度为 O(n2),对于大于1e5的复杂度,是会超时的,所以要用到下面滑动窗口的方法来解决
用i,j表示滑动窗口的左边界和右边界,通过改变i,j来改变滑动窗口的大小,可以想象成一个大小可变的窗口在字符串上移动,当这个窗口包含的元素满足条件,即包含字符串t的所有元素,记录下这个滑动窗口的长度,这些长度中的最小值就是要求的结果。
初始化 i = j = 0
① i不动, j不断向后移动,直到窗口中包含 t 中的所有元素(注意重复元素的数量)
② 从左边不断缩小窗口,即 i 向后移动,直到 i 如果再向后移动窗口内就不再包含 t 中所有元素,记录此时窗口长度
③ 让 i 再后移一位,此时窗口内已不包含 t 的所有元素,继续从①开始执行,寻找下一个满足条件的窗口长度
要解决的问题
- t 数组中各字符的数量怎么存
我采用了STL中的map类,定义 map<char,int> need , 即一个char值对于一个int值,可以把52个大小写字符的 int 先全部置0,然后遍历 t 数组,每次对应的字符加1,即 need[char]++。当窗口移动时,j 向后移动时,如果 t 中有 s[ j ] ,则need[s[ j ]] -- ,i 向后移动时,如果t 中有 s[ j ] ,则need[s[ j ]]++
2.每次都要判断need中的各主键对于的int 是否全为0,太麻烦,怎么办
可以设置一个整型变量 needlens = t 串长度,如果 need[s[ j ]] > 0 时,则needlens 减一,当need[s[ j ]] <= 0 时不减,因为此时s[ j ] 虽是 t 中字符,但窗口中的s[ j ]数量已经满足,这个是多余的;当needlens等于0时,就可以计算当前窗口的大小了
同时在 i 后移的时候,如果need[s[ i ]] < 0,表示这个s[ i ]是多余的,窗口内仍然包含 t 中所有元素,但也要need[s[ i ]]++
一点说明:示例代码中是先找到窗口内不含 t 中所有元素时的第一位,即s[i - 1 ~ j] 还包含t 中所有元素,但s[i ~ j] 中已经不包含 t 中所有元素,所以代码中用的是 i - j + 2
AC代码
//滑动窗口
#include<iostream>
#include<algorithm>
#include<string>
#include<map>
using namespace std;
string minstr(string s,string t) {
int needlens = t.length();
int minlen = 1e6+10;
int index;
map<char,int> need;
for(int i = 0; i < t.length(); i++) {
need[t[i]] = need[t[i]] + 1;
}
int i = 0, j;
for(j = 0; j < s.length(); j++) {
if(t.find(s[j]) < t.length()) {
if(need[s[j]] > 0)
needlens--;
need[s[j]] = need[s[j]] - 1;
if(needlens == 0) { //窗口内已包含 t 中所有元素
while(needlens == 0) { //i不断前移,直到窗口内不包含 t 中所有元素
if(t.find(s[i]) < t.length() && need[s[i]] == 0) {
need[s[i]] = need[s[i]] + 1;
needlens++;
}
else if(t.find(s[i]) < t.length() && need[s[i]] < 0)
need[s[i]] = need[s[i]] + 1;
i++;
}
//此时 s[i-1 ~ j]中包含 t 中所有元素,所以最小包含窗口为 j - i + 2
if(minlen > j - i + 2)
index = i - 1;
minlen = minlen > (j - i + 2) ? (j - i + 2) : minlen;
}
}
}
if(minlen == 1e6+10)
return "";
else
return s.substr(index,minlen);
}
int main()
{
string s,t;
cin>>s>>t;
cout<<"\""<<minstr(s,t)<<"\"";
return 0;
}