题目描述
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
注意:
- 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
- 如果 s 中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
示例 2:
输入:s = "a", t = "a"
输出:"a"
示例 3:
输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。
提示:
- 1 <= s.length, t.length <= 105
- s 和 t 由英文字母组成
进阶:你能设计一个在 o(n) 时间内解决此问题的算法吗?
思路
首先,我们不难想到暴力的方法,利用双指针在s中选取一块,判断是否包含t中的所有字符数量。
for (int i = 0; i < s.length(); i++) {
for (int j = i + 1; j < s.length(); j++)
if (s[i : j] 包含t中的所有字符)
更新答案
}
但是该方法的时间复杂度应该是大于的。那么如何缩小这个时间复杂度呢?
这时候就要想到滑动窗口,利用滑动窗口去判断,可以省去重复判断的步骤。
首先利用滑动窗口的框架,进行填充。
在本题中,need肯定是t中所有字符的数量,window可以是s[left:right]中涵盖t所有字符的数量,valid是window中t所有字符的总类别数。
当valid==need.size()时,表示window中涵盖t中的所有字符,开始收缩窗口,并判断此时s[left:right]是否是最小的子串。那么就可以写出本题的题解。
class Solution {
public String minWindow(String s, String t) {
HashMap<Character, Integer> window = new HashMap<>();
HashMap<Character, Integer> need = new HashMap<>();
for (char c : t.toCharArray())
need.put(c, need.getOrDefault(c, 0) + 1);
int left = 0, right = 0;
int vaild = 0;
// 记录最小字串的开始位置和长度
int start = 0, len = Integer.MAX_VALUE;
while (right < s.length()) {
char c = s.charAt(right);
right++;
if (need.getOrDefault(c, 0) != 0) {
window.put(c, window.getOrDefault(c, 0) + 1);
if (window.get(c) == need.get(c))
vaild++;
}
while (vaild == need.size()) {
if (len > right - left) {
start = left;
len = right - left;
}
char d = s.charAt(left);
left++;
if (need.getOrDefault(d, 0) != 0) {
if (window.get(d) == need.get(d))
vaild--;
window.put(d, window.get(d) - 1);
}
}
}
return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len);
}
}