题目描述
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:
If there is no such window in S that covers all characters in T, return the empty string “”.
If there is such window, you are guaranteed that there will always be only one unique minimum window in S.
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-window-substring
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
代码
用双指针 left 和 right 表示一个窗口。
right 向右移增大窗口,直到窗口包含了所有要求的字母。进行第二步。
记录此时的长度,left 向右移动,开始减少长度,每减少一次,就更新最小长度。直到当前窗口不包含所有字母,回到第 1 步。
思想有了,其实这里需要解决的问题只有一个,怎么来判断当前窗口包含了所有字母。
判断字符串相等,并且不要求顺序,之前已经用过很多次了,利用 HashMap,对于两个字符串 S = “ADOBECODEBANC”, T = “ABCB”,用 map 统计 T 的每个字母的出现次数,然后遍历 S,遇到相应的字母,就将相应字母的次数减 1,如果此时 map 中所有字母的次数都小于等于 0,那么此时的窗口一定包含了所有字母。
由于字符串中只有字母,我们其实可以不用 hashmap,可以直接用一个 int 数组,字母的 ascii 码值作为下标,保存每个字母的次数。
使用hashmap
public String minWindow(String s, String t) {
Map<Character, Integer> map = new HashMap<>();
//遍历字符串 t,初始化每个字母的次数
for (int i = 0; i < t.length(); i++) {
char char_i = t.charAt(i);
map.put(char_i, map.getOrDefault(char_i, 0) + 1);
}
int left = 0; //左指针
int right = 0; //右指针
int ans_left = 0; //保存最小窗口的左边界
int ans_right = -1; //保存最小窗口的右边界
int ans_len = Integer.MAX_VALUE; //当前最小窗口的长度
//遍历字符串 s
while (right < s.length()) {
char char_right = s.charAt(right);
//判断 map 中是否含有当前字母
if (map.containsKey(char_right)) {
//当前的字母次数减一
map.put(char_right, map.get(char_right) - 1);
//开始移动左指针,减小窗口
while (match(map)) { //如果当前窗口包含所有字母,就进入循环
//当前窗口大小
int temp_len = right - left + 1;
//如果当前窗口更小,则更新相应变量
if (temp_len < ans_len) {
ans_left = left;
ans_right = right;
ans_len = temp_len;
}
//得到左指针的字母
char key = s.charAt(left);
//判断 map 中是否有当前字母
if (map.containsKey(key)) {
//因为要把当前字母移除,所有相应次数要加 1
map.put(key, map.get(key) + 1);
}
left++; //左指针右移
}
}
//右指针右移扩大窗口
right++;
}
return s.substring(ans_left, ans_right+1);
}
//判断所有的 value 是否为 0
private boolean match(Map<Character, Integer> map) {
for (Integer value : map.values()) {
if (value > 0) {
return false;
}
}
return true;
}
使用int数组
public String minWindow(String s, String t) {
int[] map = new int[128];
// 遍历字符串 t,初始化每个字母的次数
for (int i = 0; i < t.length(); i++) {
char char_i = t.charAt(i);
map[char_i]++;
}
int left = 0; // 左指针
int right = 0; // 右指针
int ans_left = 0; // 保存最小窗口的左边界
int ans_right = -1; // 保存最小窗口的右边界
int ans_len = Integer.MAX_VALUE; // 当前最小窗口的长度
int count = t.length();
// 遍历字符串 s
while (right < s.length()) {
char char_right = s.charAt(right);
// 当前的字母次数减一
map[char_right]--;
//代表当前符合了一个字母
if (map[char_right] >= 0) {
count--;
}
// 开始移动左指针,减小窗口
while (count == 0) { // 如果当前窗口包含所有字母,就进入循环
// 当前窗口大小
int temp_len = right - left + 1;
// 如果当前窗口更小,则更新相应变量
if (temp_len < ans_len) {
ans_left = left;
ans_right = right;
ans_len = temp_len;
}
// 得到左指针的字母
char key = s.charAt(left);
// 因为要把当前字母移除,所有相应次数要加 1
map[key]++;
//此时的 map[key] 大于 0 了,表示缺少当前字母了,count++
if (map[key] > 0) {
count++;
}
left++; // 左指针右移
}
// 右指针右移扩大窗口
right++;
}
return s.substring(ans_left, ans_right + 1);
}
另外写法
#include <iostream>
#include <cassert>
using namespace std;
/// Sliding Window
/// Time Complexity: O(n)
/// Space Complexity: O(1)
class Solution {
public:
string minWindow(string s, string t) {
int tFreq[256] = {0};
for(int i = 0 ; i < t.size() ; i ++)
tFreq[t[i]] ++;
int sFreq[256] = {0};
int sCnt = 0;
int minLength = s.size() + 1;
int startIndex = -1;
int l = 0, r = -1;
while(l < s.size()){
if(r + 1 < s.size() && sCnt < t.size()){
sFreq[s[r+1]] ++;
if(sFreq[s[r+1]] <= tFreq[s[r+1]])
sCnt ++;
r ++;
}
else{
assert(sCnt <= t.size());
if(sCnt == t.size() && r - l + 1 < minLength){
minLength = r - l + 1;
startIndex = l;
}
sFreq[s[l]] --;
if(sFreq[s[l]] < tFreq[s[l]])
sCnt --;
l ++;
}
}
if( startIndex != -1 )
return s.substr(startIndex, minLength);
return "";
}
};
int main() {
cout << Solution().minWindow("ADOBECODEBANC", "ABC") << endl;
// BANC
cout << Solution().minWindow("a", "aa") << endl;
// empty
cout << Solution().minWindow("aa", "aa") << endl;
// aa
cout << Solution().minWindow("bba", "ab") << endl;
// ba
return 0;
}
优化
当T 的长度远远小于S,S中包括大量T中不存在的元素。
#include <iostream>
#include <cassert>
#include <unordered_set>
#include <vector>
using namespace std;
/// Sliding Window
/// Using filtered s, which remove all characters not in T
/// will be a good improvement when T is small and lots of character are not in S:)
///
/// Time Complexity: O(n)
/// Space Complexity: O(n)
class Solution {
public:
string minWindow(string s, string t) {
unordered_set<char> t_set;
int tFreq[256] = {0};
for(char c: t){
t_set.insert(c);
tFreq[c] ++;
}
string filtered_s = "";
vector<int> pos;
for(int i = 0; i < s.size() ; i ++)
if(t_set.find(s[i]) != t_set.end()){
filtered_s += s[i];
pos.push_back(i);
}
int sFreq[256] = {0};
int sCnt = 0;
int minLength = s.size() + 1;
int startIndex = -1;
int l = 0, r = -1;
while(l < filtered_s.size()){
if(r + 1 < filtered_s.size() && sCnt < t.size()){
sFreq[filtered_s[r+1]] ++;
if(sFreq[filtered_s[r+1]] <= tFreq[filtered_s[r+1]])
sCnt ++;
r ++;
}
else{
assert(sCnt <= t.size());
if(sCnt == t.size() && pos[r] - pos[l] + 1 < minLength){
minLength = pos[r] - pos[l] + 1;
startIndex = pos[l];
}
sFreq[filtered_s[l]] --;
if(sFreq[filtered_s[l]] < tFreq[filtered_s[l]])
sCnt --;
l ++;
}
}
if( startIndex != -1 )
return s.substr(startIndex, minLength);
return "";
}
};
int main() {
cout << Solution().minWindow("ADOBECODEBANC", "ABC") << endl;
// BANC
cout << Solution().minWindow("a", "aa") << endl;
// empty
cout << Solution().minWindow("aa", "aa") << endl;
// aa
cout << Solution().minWindow("bba", "ab") << endl;
// ba
return 0;
}