Leetcode76. 最小覆盖子串
给你一个字符串 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 的子串中,
因此没有符合条件的子字符串,返回空字符串。
方案:双指针
(滑动窗口) O(n)O(n)O(n)
过程如下:
1、遍历t字符串,用ht哈希表记录t字符串各个字符出现的次数。
2、定义两个指针jjj和iii,jjj指针用于收缩窗口,iii指针用于延伸窗口,则区间[j,i][j,i][j,i]表示当前滑动窗口。首先让i和j指针都指向字符串s开头,然后枚举整个字符串s ,枚举过程中,不断增加iii使滑动窗口增大,相当于向右扩展滑动窗口。
3、每次向右扩展滑动窗口一步,将s[i]加入滑动窗口中,而新加入了s[i],相当于滑动窗口维护的字符数加一,即hs[s[i]]++。
4、对于新加入的字符s[i]s[i]s[i],如果hs[s[i]]<ht[s[i]]hs[s[i]] < ht[s[i]]hs[s[i]]<ht[s[i]],说明当前新加入的字符s[i]是必需的,且还未到达字符串ttt所要求的数量。我们还需要事先定义一个cntcntcnt变量, cntcntcnt维护的是s字符串[j,i][j,i][j,i]区间中满足t字符串的元素的个数,记录相对应字符的总数。新加入的字符s[i]必需,则cnt++。
5、我们向右扩展滑动窗口的同时也不能忘记收缩滑动窗口。因此当hs[s[j]]>ht[s[j]hs[s[j]] > ht[s[j]hs[s[j]]>ht[s[j]时,说明hs哈希表中s[j]s[j]s[j]的数量多于ht哈希表中s[j]的数量,此时我们就需要向右收缩滑动窗口,j++并使hs[s[j]]−−hs[s[j]]--hs[s[j]]−−,即hs[s[j++]]−−hs[s[j ++ ]] --hs[s[j++]]−−。
6、当cnt==t.sizecnt == t.sizecnt==t.size时,说明此时滑动窗口包含符串 t 的全部字符。我们重复上述过程找到最小窗口即为答案。
时间复杂度分析: 两个指针都严格递增,最多移动 nnn 次,所以总时间复杂度是 O(n)O(n)O(n)。
java代码:
public static String minWindow(String s, String t) {
HashMap<Character, Integer> ht = new HashMap<>();
HashMap<Character, Integer> hs = new HashMap<>();
for (int i = 0; i < t.length(); i++) {
ht.put(t.charAt(i), ht.getOrDefault(s.charAt(i), 0) + 1);
}
String ans = "";
int len = Integer.MAX_VALUE, cnt = 0;
int j = 0;
for (int i = 0; i < s.length(); i++) {
hs.put(s.charAt(i), hs.getOrDefault(s.charAt(i), 0) + 1);
if (ht.containsKey(s.charAt(i)) && hs.get(s.charAt(i)) <= ht.get(s.charAt(i))) cnt++;
while (j < i && (!ht.containsKey(s.charAt(j)) || hs.get(s.charAt(j)) > ht.get(s.charAt(j)))) {
int count = hs.get(s.charAt(j)) - 1;
hs.put(s.charAt(j), count);
j++;
}
if (cnt == t.length() && i - j + 1 < len) {
len = i - j + 1;
ans = s.substring(j, i + 1);
}
}
return ans;
}