题目
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
注意:
- 对于
t中重复字符,我们寻找的子字符串中该字符数量必须不少于t中该字符数量。 - 如果
s中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入: s = "ADOBECODEBANC", t = "ABC"
输出: "BANC"
解释: 最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。
示例 2:
输入: s = "a", t = "a"
输出: "a"
解释: 整个字符串 s 是最小覆盖子串。
示例 3:
输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。
提示:
m == s.lengthn == t.length1 <= m, n <= 10^5s和t由英文字母组成
思路
m,n最大值为105,o(n2)肯定会超时,所以先考虑循环一次能否得到答案- 读题可知不用考虑字符顺序,且
t中会存在重复字符,所以字符数量也是需要在考虑的范围中,如此可以考虑用Map记录所有t中的字符和对应数量. - 循环
s,当Map中存在时,count++,Map中value--,那么当count = t.length()我们就获得了第一个包含t所有字符的子串. - 这个时候吐出前面拿到子串左边的第一个字符,
count--,这样让循环继续试试后面会不会有更短的子串
典型滑动窗口即可解决。
代码
public String minWindow(String s, String t) {
//用来存放t所有char和对应的数量
Map<Character, Integer> charIndexMap = new HashMap<>()
//把所有t放进map中
for(char c : t.toCharArray()) charIndexMap.put(c, charIndexMap.getOrDefault(c, 0) + 1);
//当前滑动窗口最左下标
int left = 0;
//当前滑动窗口包含t中元素的数量
int count = 0;
//最小子串的最左下标
int minStart = 0;
//最小子串的长度(记录长度是为了方便后面比较长短)
int minLength = Integer.MAX_VALUE;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (charIndexMap.containsKey(c)) {
if (charIndexMap.get(c) > 0) {
count++;
}
//无论当前滑动窗口是否有t的元素,都应记录已出现的元素数量,可能为负数
charIndexMap.put(c, charIndexMap.get(c) - 1);
}
while (count == t.length()) {
//当滑动窗口包含t的所有字符时判断这个窗口是否更短
if (i - left + 1 < minLength) {
minLength = i - left + 1;
minStart = left;
}
//开始吐出最左的第一个t中的元素
if (charIndexMap.containsKey(s.charAt(left))) {
charIndexMap.put(s.charAt(left), charIndexMap.get(s.charAt(left)) + 1);
//这就是上面可能为负数的操作的原因,过多的重复元素不好使
if(charIndexMap.get(s.charAt(left)) > 0)count--;
}
//更新当前滑动窗口边界
left++;
}
}
if(minLength == Integer.MAX_VALUE) return "";
return s.substring(minStart, minStart + minLength);
}