76. 最小覆盖子串
题目
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
注意:1.如果 s 中存在这样的子串,我们保证它是唯一的答案。2.最小子串中不仅需要包含t中出现的字符,字符出现的次数也有要求。
示例 1:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
示例 2:
输入:s = "aa", t = "aa"
输出:"aa"
滑动窗口法
很显然要使用滑动窗口法,但关键的问题是如何得知当前子串是否包含了t中所有字符。
可以使用一个计数器cnt:遍历right时HashMap 中的 value 减一,如果减1后仍大于等于0说明该字符在t中出现过,cnt 自增1;当cnt等于t长度时说明已经包含了t中所有字符,此时left右移,遍历left时HashMap 中的 value 加一,如果加1后大于0说明移出掉的是t中出现的字符,cnt自减1。
具体步骤:
(1)先扫描一遍T,把对应的字符及其出现的次数存到 HashMap 中。
(2)然后开始遍历S,就把遍历到的字母对应的 HashMap 中的 value 减一,如果减1后仍大于等于0,cnt 自增1。
(3)如果 cnt 等于T串长度时,开始循环,纪录一个字串并更新最小字串值。然后将子窗口的左边界向右移,如果某个移除掉的字母是T串中不可缺少的字母,那么 cnt 自减1,表示此时T串并没有完全匹配。
public String minWindow(String s, String t) {
if(s.length()<t.length()) return "";
int left=0,right=0,sLen=s.length(),tLen=t.length(),cnt=0,minLen=Integer.MAX_VALUE,l=left,r=right;
int[] charCount=new int[128];
for(int i=0;i<tLen;++i) charCount[t.charAt(i)]++;
while(right<sLen){
//如果减1后的映射值仍大于等于0,说明当前遍历到的字母是T串中的字母,cnt自增1
if(--charCount[s.charAt(right++)]>=0) cnt++;
while(cnt==tLen){
if(minLen>right-left){
minLen=right-left;
l=left;
r=right;
}
//如果加1后的值大于0了,说明此时少了一个T中的字母,那么 cnt 值就要减1
if(++charCount[s.charAt(left)]>0) cnt--;
left++;
}
}
return s.substring(l,r);
}