给你一个字符串 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 的子串中, 因此没有符合条件的子字符串,返回空字符串。 需要两个HashMap,一个记录字符串t中字符出现的频率,一个用于统计当前滑动窗口中在字符串t中字符的出现数量,另外两个一个have用于统计当前滑动窗口中满足条件的字符数,一个tcount统计总共字符数量。
class Solution {
public String minWindow(String s, String t) {
// 先判断一下s和t的长度,如果任何一个长度为0,返回空字符串
if (s.length() == 0 || t.length() == 0) {
return "";
}
// 定义两个哈希表,一个统计t中字符的频率,另一个统计滑动窗口中字符的频率
Map<Character, Integer> mapT = new HashMap<>();
Map<Character, Integer> mapW = new HashMap<>();
// 统计t中字符的频率
for (char c : t.toCharArray()) {
mapT.put(c, mapT.getOrDefault(c, 0) + 1);
}
// 统计t中不同字符的数量
int tCount = mapT.size();
// have变量用于统计滑动窗口中满足t中字符频率的字符数量
int have = 0;
// 定义结果字符串的起点和长度
int resStart = 0, resLen = Integer.MAX_VALUE;
// 滑动窗口的左右边界
int left = 0, right = 0;
// 遍历字符串s
while (right < s.length()) {
char rightChar = s.charAt(right);
// 如果当前字符在t中,更新滑动窗口中的字符频率
if (mapT.containsKey(rightChar)) {
mapW.put(rightChar, mapW.getOrDefault(rightChar, 0) + 1);
// 如果滑动窗口中当前字符的频率与t中字符的频率相等,更新have
if (mapT.get(rightChar).equals(mapW.get(rightChar))) {
have++;
}
}
// 当滑动窗口满足条件时,尝试缩小窗口
while (have == tCount) {
// 如果当前窗口的长度小于之前记录的最小长度,更新最小长度和起点
if (right - left + 1 < resLen) {
resLen = right - left + 1;
resStart = left;
}
// 移动左边界,缩小窗口
char leftChar = s.charAt(left);
// 如果左边界字符在t中,更新滑动窗口中的字符频率和have
if (mapT.containsKey(leftChar)) {
mapW.put(leftChar, mapW.get(leftChar) - 1);
// 如果滑动窗口中当前字符的频率小于t中字符的频率,更新have
if (mapW.get(leftChar) < mapT.get(leftChar)) {
have--;
}
}
left++;
}
// 移动右边界,扩大窗口
right++;
}
// 如果没有找到符合条件的窗口,返回空字符串
if (resLen == Integer.MAX_VALUE) {
return "";
}
// 返回结果字符串
return s.substring(resStart, resStart + resLen);
}
}