- 题目描述
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
注意:如果 s 中存在这样的子串,我们保证它是唯一的答案。
提示:
1 <= s.length, t.length <= 105
s 和 t 由英文字母组成
进阶:你能设计一个在 o(n) 时间内解决此问题的算法吗?
来源:LeetCode
- 示例
- 示例 1:
输入:s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC” - 示例 2:
输入:s = “a”, t = “a”
输出:“a”
- 思路分析
- 直接暴力求解不太现实,如果字符串很长的话,效率会非常低。因为要找的是一个符合条件的子串,可以想到会用滑动窗口法:即找到一个长度最小的窗口,包含目标字符串中的所有字符。滑动窗口法有两种情况:①窗口大小固定;②窗口大小不固定。此处即第二种。
- 在寻找窗口之前,我们先处理一下目标字符串 t t t。因为窗口需要满足目标字符串的长度和对应字符的个数,我们将 t t t以 < C h a r a c t e r − I n t e g e r > <Character-Integer> <Character−Integer>的形式保存在字典里,记录每个字符以及对应个数。保存 l e n t len_t lent,表示字符串 t t t的长度,用来监控窗口中的符合要求的字符总长度是否已经达到了字符串 t t t的长度。
- 在找窗口的过程中,我们记录两个值: s t a r t start start和 e n d end end。 s t a r t start start记录窗口的左边界, e n d end end记录窗口的右边界,窗口的内容即为 [ s t a r t , e n d ) [start, end) [start,end)。从 0 0 0开始,我们将 e n d end end向右滑动,遇到一个字符即判断它是否是目标字符串中的字符:如果是,就将字典中该字符的个数 − 1 -1 −1,表示找到了一个。如果在 − 1 -1 −1之前,该字符剩余要找的个数 > = 1 >=1 >=1,将 l e n t − 1 len_t-1 lent−1。需要保存 l e n t len_t lent的原因就在这里:比如 s = A A B s=AAB s=AAB, t = A B t=AB t=AB,如果不保存 l e n t len_t lent,而只看有多少个在 t t t中的字符,那么 s s s中的 A A AA AA也会被看做是一个符合要求的窗口。但实际上,检查到第一个 A A A时,字典中的 < A : 1 > <A:1> <A:1>会变成 < A : 0 > <A:0> <A:0>, l e n t − 1 len_t-1 lent−1;检查到第二个 A A A时,字典中的 < A : 0 > <A:0> <A:0>会变成 < A : − 1 > <A:-1> <A:−1>,但由于本来是 < A : 0 > <A:0> <A:0>,说明已经不需要 A A A了,因此 l e n t len_t lent不会 − 1 -1 −1,会继续检查下一个字符。
- 当 l e n t len_t lent变为 0 0 0, 说明已经找到了一个符合条件的窗口。而我们需要的窗口尽可能小,因此我们从 0 0 0开始向右移动左边界 s t a r t start start,并检查遇到的每一个字符:如果不要这个字符,剩下的字符串是否仍然使 l e n t = 0 len_t=0 lent=0。具体方法是:如果这个字符不在t中,则可以去掉;如果在 t t t中,则需要将其字典中的值 + 1 +1 +1,如果该值现在 > 0 >0 >0,说明这个字符是必不可少的, l e n t + 1 len_t+1 lent+1。如果 l e n t len_t lent变为 > 0 >0 >0,说明现在的窗口不满足条件,因此 e n d end end需要再次开始向右滑动,重复上一步的过程。
- JAVA实现
class Solution {
public String minWindow(String s, String t) {
HashMap map = new HashMap();
int len_s = s.length();
int len_t = t.length();
if(len_s<len_t) return "";
for(int i=0; i<len_t; i++) {
char ch = t.charAt(i);
if(!map.containsKey(ch)) map.put(ch, 1);
else map.put(ch, (int)map.get(ch)+1);
}
int end = 0, start = 0;
int minLeft = 0, minRight = Integer.MAX_VALUE; //这样设置保证如果有一个解,则一定能够使minLeft和minRight被改变
while(end < len_s) {
if(map.containsKey(s.charAt(end))) {
if((int)map.get(s.charAt(end)) >= 1) len_t--;
map.put(s.charAt(end), (int)map.get(s.charAt(end)) - 1);
}
end++;
while(len_t == 0) { //缩小左边界"到第一个出现t中字母的位置
if(map.containsKey(s.charAt(start))) {
map.put(s.charAt(start), (int)map.get(s.charAt(start))+1);
if((int)map.get(s.charAt(start)) > 0) len_t++;
}
if(end - start < minRight - minLeft) {
minRight = end;
minLeft = start;
}
start++;
}
}
return minRight==Integer.MAX_VALUE?"":s.substring(minLeft, minRight);
}
}