题目:
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
注意:如果 s 中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入:s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC”
示例 2:
输入:s = “a”, t = “a”
输出:“a”
提示:
1 <= s.length, t.length <= 105
s 和 t 由英文字母组成
进阶:你能设计一个在 o(n) 时间内解决此问题的算法吗?
思路
这是我第一次硬刚困难难度的题目,其恶心程度远超我想象。要在O(n)的时间内解决问题,那肯定不能用暴力解法,只能一次遍历字符串,然后计算得到结果。
因此,肯定是要用滑动窗口来解决问题。
1、我们在字符串S中使用双指针中的左右指针技巧,初始化left = right = 0,把索引左闭右开区间[left, right)称为一个「窗口」。
2、我们先不断地增加right指针扩大窗口[left, right),直到窗口中的字符串符合要求(包含了T中的所有字符)。
3、此时,我们停止增加right,转而不断增加left指针缩小窗口[left, right),直到窗口中的字符串不再符合要求(不包含T中的所有字符了)。同时,每次增加left,我们都要更新一轮结果。
4、重复第 2 和第 3 步,直到right到达字符串S的尽头。
代码
public String minWindow(String s, String t) {
//创建两个HashMap用来计数,need记录目标字符串中每个字符出现的次数,window记录窗口覆盖的字符串
HashMap<Character,Integer> window= new HashMap<>();
HashMap<Character,Integer> need= new HashMap<>();
//初始化need和window,其中need需要注意的是,有些字符可能出现多次
for(int i=0;i<t.length();i++){
Integer val=need.getOrDefault(t.charAt(i),0);
val++;
need.put(t.charAt(i),val);
}
for(int j=0;j<s.length();j++){
window.put(s.charAt(j),0);
}
//初始化窗口长度,窗口左闭右开
int left=0,right=0,valid=0,len=9999999,start=0;
//先让窗口变大,直到能够覆盖目标字符串
while(right<s.length()){
//右指针移,扩大窗口
Character c=s.charAt(right);
right++;
if(need.containsKey(c)){
Integer val1=window.get(c);
val1++;
window.put(c,val1);
if(window.get(c).equals(need.get(c))){
// if(window.get(c)==need.get(c)){
valid++;
}
}
//当valid等于need的大小之后,说明此时窗口已经能够包括所有目标字符串了,此时要缩小窗口
while(valid==need.size()){
if(right-left<len){
start=left;
len=right-left;
}
//左指针移动,缩小窗口
Character d=s.charAt(left);
left++;
if(need.containsKey(d)){
if(window.get(d).equals(need.get(d)))
valid--;
Integer val2=window.get(d);
val2--;
window.put(d,val2);
}
}
}
if(len==9999999||t.length()>s.length()){
return "";
}else if(s.equals(t)){
return s;
}else {
return s.substring(start,start+len);
}
}
注意
然后需要注意的是,在力扣的测试案例中,最后有一个特别特别大的测试案例,那个案例卡了我特别久,原因是因为定义的Map里的Integer是对象,Integer会缓存频繁使用的数值[-128,127],超过此范围就会new一个对象,导致使用“==”错误,改为equals()即可。