给你一个字符串 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 的子串中, 因此没有符合条件的子字符串,返回空字符串。
思想:用滑动窗口移动,在一个s的子串中找到包含t的最短子串,用两个hashmap存储字符串中对应字符及字符数量,因为不用考虑顺序,所以只需考虑字符数量即可。
代码:
class Solution {
public String minWindow(String s, String t) {
//判断是能满足子串
if (s.length() < t.length()) return "";
//记录字符及字符数量
Map<Character, Integer> need = new HashMap<>();//t
Map<Character, Integer> window = new HashMap<>();//窗口
// 统计 t 中每个字符的需求数量
//need.getOrDefault(c, 0),如果need.get(c)存在就返回该结果,不存在就0
for (char c : t.toCharArray()) {
need.put(c, need.getOrDefault(c, 0) + 1);
}
int left = 0;
int right = 0;
int start = 0;//起点
int minLen = Integer.MAX_VALUE;
int valid = 0;//有效flag,看下面解释
while (right < s.length()) {
char c = s.charAt(right);
right++;//向右移动
//如果字符c是t中的字符
if (need.containsKey(c)) {
//统计window中c字符的数量
window.put(c, window.getOrDefault(c, 0) + 1)
//这里细节只能用equals判断
//如果window中的c字符的数量大于t中字符数量,vaild仍不会重复增加
//所以不可以用大于等于
if (window.get(c).equals(need.get(c))) {
valid++;
}
}
// vaild等于need.size()指的是t中每个字符的数量都要小于等于window中的数量
//这时候开始缩减window,left++
while (valid == need.size()) {
//不是记录子串的长度,而是做个标记,方便后面截取子串
if (right - left < minLen) {
minLen = right - left;
start = left;
}
// 收缩窗口
char leftChar = s.charAt(left);
left++;
if (need.containsKey(leftChar)) {
// 这里用等于判断也是细节,说明leftChar在window中的数量刚好等于t中的数量
//所以向右移动会使得window中的数量小于t中的数量,所以valid--;
//如果window中的leftChar数量大于等于t中的数量便还可以向右缩减窗口大小
if (window.get(leftChar).equals(need.get(leftChar))) {
valid--;
}
// 更新窗口中该字符的数量
window.put(leftChar, window.getOrDefault(leftChar, 0) - 1);
}
}
}
// 返回最小子串
return minLen == Integer.MAX_VALUE ? "" : s.substring(start, start + minLen);
}
}