Given a string S and a string T, find the minimum window in S which will contain all the characters in T in complexity O(n).
Example:
Input: S = “ADOBECODEBANC”, T = “ABC”
Output: “BANC”
(Note: T中可能会有重复字母)
method 1 sliding window
摘自leetcode discuss
- 首先使用一个map记录字符串t中出现的字符及其频率
- 首先从头开始,在s中寻找包含t的一个区间,然后滑动窗口求最小,如何寻找包含t的区间,使用计数器count来实现。
- 如何滑动窗口求最小: 1. 检查左指针所指字符是否属于t,如果不属于,可以直接滑动窗口,缩小窗口; 2.如果属于t,仍然滑动窗口,并且移动右指针,表示我们希望在右指针扩大的图中找到我们刚刚删去的字符
String minWindow(String s, String t) {
HashMap<Character, Integer> map = new HashMap();
for(char c : t.toCharArray()){
if(map.containsKey(c)){
map.put(c, map.get(c)+1);
}
else{
map.put(c, 1);
}
}
int left = 0, minLeft=0, minLen =s.length()+1, count = 0;
for(int right = 0; right<s.length(); right++){
char r = s.charAt(right);
if(map.containsKey(r)){//the goal of this part is to get the first window that contains whole t
map.put(r, map.get(r)-1);
if(map.get(r)>=0) count++;//identify if the first window is found by counting frequency of the characters of t showing up in S
}
while(count == t.length()){//if the count is equal to the length of t, then we find such window
if(right-left+1 < minLen){//jsut update the minleft and minlen value
minLeft = left;
minLen = right-left+1;
}
char l = s.charAt(left);
if(map.containsKey(l)){//starting from the leftmost index of the window, we want to check if s[left] is in t. If so, we will remove it from the window, and increase 1 time on its counter in hashmap which means we will expect the same character later by shifting right index. At the same time, we need to reduce the size of the window due to the removal.
map.put(l, map.get(l)+1);
if(map.get(l)>0) count--;
}
left++;//if it doesn't exist in t, it is not supposed to be in the window, left++. If it does exist in t, the reason is stated as above. left++.
}
}
return minLen==s.length()+1?"":s.substring(minLeft, minLeft+minLen);
}
method 2
使用vector的简化版本,但思想是一致的
string minWindow(string s, string t) {
vector<int> map(128,0);
for(auto c: t) map[c]++;
int counter=t.size(), begin=0, end=0, d=INT_MAX, head=0;
while(end<s.size()){
if(map[s[end++]]-->0) counter--; //in t
while(counter==0){ //valid
if(end-begin<d) d=end-(head=begin);
if(map[s[begin++]]++==0) counter++; //make it invalid
}
}
return d==INT_MAX? "":s.substr(head, d);
}
https://leetcode.com/problems/minimum-window-substring/discuss/26808/Here-is-a-10-line-template-that-can-solve-most-‘substring’-problems
提供了在一个string中找到substring的模板
反面案例
自己写的代码,但是由于复杂度略高,一直卡在最后一个超长的测试用例上,为以后自己提供教训
- 一开始直接使用string来保存每次得到的"最小区间",但这样使用substr无疑会增加复杂度,不如使用两个int来取区间,速度更快一点
- 每次找新窗口都是移动左指针来寻找新窗口,不符合sliding window两根指针的用途
int findIntersection(string s, map<char, int> tMap, int start, map<char, int>& alphabet){
int end = start;
for (int i = start; i < s.size(); i++){
char ch = s[i];
if (tMap.count(ch)){
tMap[ch]--;
if (!alphabet.count(ch))
alphabet[ch] = 1;
else
alphabet[ch]++;
if(tMap[ch] == 0) tMap.erase(s[i]);
}
if (tMap.size() == 0){
end = i;
break;
}
}
return end;
}
string minWindow(string s, string t) {
if (t.size() == 0) return "";
if (t.size() == 1){
if (s.find(t) != s.npos) return t;
}
map<char, int> tMap;
for (int i = 0; i < t.size(); i++){
if (!tMap.count(t[i])) tMap[t[i]] = 1;
else tMap[t[i]]++;
}
int minLength = 0;
int minStart = 0;
int curLength = 0;
for (int i = 0; i <s.size(); i++){
if (!tMap.count(s[i])) continue;
map<char, int> alphabet;
int high = findIntersection(s, tMap, i, alphabet);
if (minLength != 0 && high - i + 1 >= minLength) continue;
if (high != i){
int low = i;
while (low <= high){
char ch = s[low];
if (tMap.count(ch)){
alphabet[ch]--;
if (alphabet[ch] == 0){
curLength = high - low + 1;
if (minLength == 0){
minLength = curLength;
minStart = low;
break;
}
else if (curLength < minLength){
minLength = curLength;
minStart = low;
break;
}
}
}
else
low++;
}
}
}
return (minLength == 0)? "":s.substr(minStart, minLength);
}
summary
- sliding window 思想:1. 使用两根指针,左指针缩小窗口(contract),右指针滑动寻找新的符合要求的窗口; 2. 为了满足要求,当左指针消去一个符合要求的字符时,我们会要求在右指针上获得一个符合要求的字符
- 使用count计数器和map联合的办法,确保我们得到符合要求的区间