- Minimum Window Substring
中文English
Given two strings source and target. Return the minimum substring of source which contains each char of target.
Example
Example 1:
Input: source = “abc”, target = “ac”
Output: “abc”
Example 2:
Input: source = “adobecodebanc”, target = “abc”
Output: “banc”
Explanation: “banc” is the minimum substring of source string which contains each char of target “abc”.
Example 3:
Input: source = “abc”, target = “aa”
Output: “”
Explanation: No substring contains two ‘a’.
Challenge
O(n) time
Notice
If there is no answer, return “”.
You are guaranteed that the answer is unique.
target may contain duplicate char, while the answer need to contain at least the same number of that char.
解法1:
这题思路就是用同向双指针,但是我觉得相当不容易。
- p1从0开始,p2先往右移动,一直到p1…p2之间的窗口覆盖所有的target字母。然后p1再往右移动一个,若
窗口已经不能覆盖所有字母了,则p2又往右移动一个,直到边界。 - count表示当前窗口(p1+1…p2)里面还缺count个target的元素。所以一旦count>0,p2必须往右移动。
- if (mp[source[p1]] == 0) count++;即source[p1]是一个target里面的元素,此时window罩不住target里面的元素了。注意p1在p2后面,p2已经处理过了,所以mp[source[p1]]=0的话一定是一个target里面的元素。
代码如下:
class Solution {
public:
/**
* @param source : A string
* @param target: A string
* @return: A string denote the minimum window, return "" if there is no such a string
*/
string minWindow(string &source , string &target) {
int ns = source.size();
int nt = target.size();
map<char, int> mp;
for (int i = 0; i < nt; ++i) {
mp[target[i]]++;
}
int count = mp.size();
int p1 = 0, p2 = 0;
int minLen = INT_MAX;
string minResult;
while(p1 < ns) {
while(p2 < ns && count > 0) {
mp[source[p2]]--;
if (mp[source[p2]] == 0) count--;
p2++;
if (count == 0) break;
}
if (count == 0) {
int curWinSize = p2 - p1;// + 1;
if (curWinSize < minLen) {
minLen = curWinSize;
minResult = source.substr(p1, curWinSize);
}
}
if (mp[source[p1]] == 0) count++;
mp[source[p1]]++;
p1++;
}
return minResult;
}
};
解法2:参考了Grandyang的解法。slidingwindow。很妙。
class Solution {
public:
/**
* @param source: A string
* @param target: A string
* @return: A string denote the minimum window, return "" if there is no such a string
*/
string minWindow(string &source, string &target) {
int source_len = source.size();
int target_len = target.size();
vector<int> freq_target(256, 0);
int cnt = 0;
for (int i = 0; i < target_len; i++) {
freq_target[target[i]]++;
}
bool found = false;
int left = 0;
int saved_left = 0, saved_right = source_len - 1;
for (int i = 0; i < source_len; i++) {
if (--freq_target[source[i]] >= 0) cnt++;
while (cnt == target_len) {
if (saved_right - saved_left >= i - left) {
saved_right = i;
saved_left = left;
found = true;
}
if (++freq_target[source[left]] > 0) cnt--;
++left;
}
}
if (found) return source.substr(saved_left, saved_right - saved_left + 1);
return "";
}
};
解法3:同向双指针模板。思路同lintcode 1169.
https://blog.youkuaiyun.com/roufoo/article/details/127944794?spm=1001.2014.3001.5501
class Solution {
public:
/**
* @param source: A string
* @param target: A string
* @return: A string denote the minimum window, return "" if there is no such a string
*/
string minWindow(string &source, string &target) {
int lenS = source.size();
int lenT = target.size();
if (lenS < lenT) return "";
string res;
vector<int> freq(128, 0);
for (int i = 0; i < lenT; i++) freq[target[i]]++;
int count = lenT, j = 0, minWin = lenS;
for (int i = 0; i < lenS; i++) {
j = max(i, j); //i窗口左边界,j窗口右边界。
while (count > 0 && j < lenS) {
if (freq[source[j]] > 0) count--;
freq[source[j]]--;
j++;
}
if (count == 0) {
if (minWin >= j - i) {
minWin = j - i;
res = source.substr(i, j - i);
}
if (freq[source[i]] == 0) count++; //此时source[i]是一个在target中的字符,即将移出窗口i..j,所以count++。注意target中可能有多个source[i],不过没关系,count==0说明target中的字符都在source[i..j]中找到且个数刚刚好,移出一个,那么count++。
freq[source[i]]++;
} else { //此时count > 0,说明j已经到末尾,i..j之间这么大的距离都不满足要求,那i也不用往后面移动了,退出循环。
break;
}
}
return res;
}
};
二刷:
class Solution {
public:
/**
* @param source: A string
* @param target: A string
* @return: A string denote the minimum window, return "" if there is no such a string
*/
string minWindow(string &source, string &target) {
int lenS = source.size();
int lenT = target.size();
if (lenS < lenT) return "";
int count = lenT;
vector<int> freq(128, 0);
for (int i = 0; i < lenT; i++) {
freq[target[i]]++;
}
int left = 0, right = 0, minLen = lenS + 1;
string minLenStr = "";
for (left = 0; left < lenS; left++) {
right = max(left, right);
while (count > 0 && right < lenS) {
if (freq[source[right]] > 0) {
count--;
}
freq[source[right]]--;
right++;
}
//if (right == lenS && count > 0) return "";
if (count == 0) {
if (minLen > right - left) { //not right-left+1 as right++ in while()
minLen = right - left;
minLenStr = source.substr(left, right - left);
}
}
if (freq[source[left]] == 0) count++; //这一步很重要
freq[source[left]]++;
}
return minLenStr;
}
};
注意上面的if (freq[source[left]] == 0) count++;很重要。
如果source=“tttttttts”, target = “tts”, 那么,第一个for循环扫描target,得到freq[t]=2。然后第二个for循环的while循环会一直往右扫描,直到最后的tts,这样freq[t]=-6 (left=0, right =8), count=0 。然后for循环会移动left,这里right不会动因为count==0,每次for迭代freq[t]++。当left=6时,freq[t]=6,此时可以认为这个t是source[left, right]里面的一个target的字母,所以count++。
三刷:也是同向双指针。
注意同向双指针有两种。一种是上面的二刷 – 先尽量把右指针往右移动,直到出现一个可行解,然后再把左指针挨个挨个往右移,每次如果还是可行解的话则记录窗口大小和相应值。用我自己的总结来说就是大力移右,挨个移左。
另一种就是这里的方法–先挨个挨个把右指针往右移,如果出现了一个可行解则尽量把左指针往右移,在可行解的范围内能移动多少移动多少,每次记录窗口大小和相应值。用我自己的总结来说就是挨个移右,大力移左。
注意:validCount的加减都必须根据freqS[c] == freqT[c],而不是>或>=!
class Solution {
public:
string minWindow(string s, string t) {
int sLen = s.size(), tLen = t.size(), needCount = 0;
vector<int> freqS(128, 0), freqT(128, 0);
for (int i = 0; i < tLen; i++) {
if (freqT[t[i]] == 0) needCount++;
freqT[t[i]]++;
}
int p1 = 0, p2 = 0;
int validCount = 0;
int start = -1, len = sLen;
while (p2 < sLen) {
char c = s[p2];
freqS[c]++;
if (freqT[c] > 0) {
if (freqS[c] == freqT[c]) validCount++;
}
p2++;
while (validCount == needCount) {
c = s[p1];
if (len >= p2 - p1) {
len = p2 - p1;
start = p1;
}
if (freqS[c] == freqT[c]) {
validCount--;
}
freqS[c]--;
p1++;
}
}
return start >= 0 ? s.substr(start, len) : "";
}
};