76.最小覆盖子窜
解题思路:双指针滑动窗口
package leadcode;
import java.util.HashMap;
import java.util.Map;
/**
* @author : icehill
* @description : 最小覆盖子窜
* 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果s中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
* 注意:如果s中存在这样的子串,我们保证它是唯一的答案。
* 示例 1:
* 输入:s = "ADOBECODEBANC", t = "ABC"
* 输出:"BANC"
* 示例 2:
* 输入:s = "a", t = "a"
* 输出:"a"
* 提示:
* 1 <= s.length, t.length <= 105
* s 和 t 由英文字母组成
* 进阶:你能设计一个在 o(n) 时间内解决此问题的算法吗?
* <p>
* 来源:力扣(LeetCode)
* 链接:https://leetcode-cn.com/problems/minimum-window-substring
* 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
* 题目分析:
* 需要找到s的子窜,并且子窜必须包含t中所有的字符,且字符出现的频率必须大于t
* 例如s=abcb t=aab,s中无法覆盖t
* 1.暴力解法(不考虑)
* 枚举s所有长度大于t的子窜,判断子窜是否覆盖t
* 时间复杂度:O(n^3) 空间复杂度O(n) (n为s的长度,求s的子窜,n^2,对于子窜,再判断频数跟t比较n,所以总的n^3)
* 由于子窜的判断中做了很多重复的工作,或者没必要的工作,所以我们不考虑这种解法
* 2.我们使用双指针的滑动窗口解题
* 例如:s=aadobecodebanc t=abc
* a.移动右指针找到第一个覆盖t
* s=[aadobec]odebanc
* b.移动左指针,直到不满足覆盖t
* s=a[adobec]odebanc
* c.右指针向右移动并探测,如果找到一个属于t的字符,重复b尝试移动右指针,记录当前窗口是否是最小值
* s=a[adobecodeb]anc
* s=aadobe[codeba]nc
* s=aadobecode[banc]
* d.返回最小子窜
* @date : 2021-05-18
*/
public class Solution76 {
public static void main(String[] args) {
Solution76 solution76 = new Solution76();
// System.out.println(solution76.minWindow("ADOBECODEBANC", "ABC"));
System.out.println(solution76.minWindow("bba", "ab"));
}
public String minWindow(String s, String t) {
Map<Character, Integer> tMap = new HashMap<>();
Map<Character, Integer> targetMap = new HashMap<>();
//把t转换成哈希表,并统计数量
for (int i = 0; i < t.length(); i++) {
if (!tMap.containsKey(t.charAt(i))) {
tMap.put(t.charAt(i), 1);
targetMap.put(t.charAt(i), 0);
} else {
tMap.put(t.charAt(i), 1 + tMap.get(t.charAt(i)));
}
}
int count = 0;
int start = -1;
int end = -1;
//移动右指针,直到找到第一个符合的子窜
for (int j = 0; j < s.length(); j++) {
if (tMap.containsKey(s.charAt(j))) {
if (start == -1) {
start = j;
}
targetMap.put(s.charAt(j), 1 + targetMap.get(s.charAt(j)));
if (targetMap.get(s.charAt(j)) <= tMap.get(s.charAt(j))) {
count++;
}
}
if (count >= t.length()) {
end = j;
break;
}
}
if (count != t.length()) {
return "";
}
//移动左指针,直到刚好满足覆盖子窜,就是当前右指针所在位置对应的最小子窜
while (start < end + 1) {
if (targetMap.get(s.charAt(start)) == null) {
start++;
continue;
}
if (targetMap.get(s.charAt(start)) > tMap.get(s.charAt(start))) {
targetMap.put(s.charAt(start), targetMap.get(s.charAt(start)) - 1);
start++;
} else {
break;
}
}
int slow = start;
//移动右指针,扩大滑动窗口
for (int fast = end + 1; fast < s.length(); fast++) {
if (tMap.containsKey(s.charAt(fast))) {
targetMap.put(s.charAt(fast), 1 + targetMap.get(s.charAt(fast)));
//移动左指针,缩小滑动窗口
while (slow < fast) {
if (targetMap.get(s.charAt(slow)) == null) {
slow++;
continue;
}
if (targetMap.get(s.charAt(slow)) > tMap.get(s.charAt(slow))) {
targetMap.put(s.charAt(slow), targetMap.get(s.charAt(slow)) - 1);
slow++;
} else {
break;
}
}
if (fast - slow < end - start) {
start = slow;
end = fast;
}
}
}
return s.substring(start, end + 1);
}
}