题目:
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
注意:如果 s 中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
示例 2:
输入:s = "a", t = "a"
输出:"a"
题解:
小知识:批量修改变量名操作:shift+F6选中变量--->修改变量--->Enter回车标签:。
labuladong的框架:
滑动窗口框架(子字符串匹配问题)
int left = 0, right = 0;
while (right < s.size()) {
window.put(s[right],window.getOrDefault(s[right], 0) + 1);
right++;
while (valid == need.size()) {
window.put(s[left],window.get(s[left]) - 1);
left++;
}
}
方法一:按照框架的做法:
import java.util.*;
class Solution {
public String minWindow(String s, String t) {
// 为什么用HashMap, 不用HashSet?
// 因为子串中可能有重复字符, 需要以k,v的形式, 记录每个字符的出现次数
// 整个过程中, needs只初始化一次, window中的元素一直在变化
HashMap<Character, Integer> needs = new HashMap<>();
HashMap<Character, Integer> window = new HashMap<>();
// 初始化needs, 作为后面判断窗口是否符合条件的依据
for (int i = 0; i < t.length(); i++) {
needs.put(t.charAt(i), needs.getOrDefault(t.charAt(i), 0) + 1);
}
int left = 0, right = 0;
// valid代表有多少个字符满足条件了, 如果vaild == needs.size(), 就认为凑满了
int valid = 0;
// 记录最小覆盖字串的起始索引及长度
int start = 0, len = Integer.MAX_VALUE;
// 右指针超出尾部就算结束
while (right < s.length()) {
// 右指针移动
char c1 = s.charAt(right);
// 判断取出的字符是否在字串中
if (needs.containsKey(c1)) {
window.put(c1, window.getOrDefault(c1, 0) + 1);
// 是否凑满need中的一个字符: 一个字符在need中出现了n次, 如果window中也累计了n次, 就认为凑满了
if (window.get(c1).equals(needs.get(c1))) {
valid++;
}
}
right++;
// 判断是否需要收缩(已经找到合适的覆盖串)
while (valid == needs.size()) {
if (right - left < len) {
start = left;
len = right - left;
}
char c2 = s.charAt(left);
left++;
if (needs.containsKey(c2)) {
window.put(c2, window.getOrDefault(c2, 0) - 1);
// 如果不满足needs中的元素要求
if (window.get(c2) < (needs.get(c2))) {
valid--;
}
}
}
}
return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len);
}
}
public class Main {
public static void main(String[] args) {
String s = "ADOBECODEBANC";
String t = "ABC";
Solution solution = new Solution();
String res = solution.minWindow(s, t);
System.out.println("结果:" + res);
}
}
方法二:
滑动窗口,使用数组来初始化每一个字符出现的次数。然后使用滑动窗口寻找包含字符串的最短字符串。
import java.util.*;
class Solution {
public String minWindow(String s, String t) {
if (s == null || t == null || s.length() == 0 || t.length() == 0) return "";
// 定义一个数字,用来记录字符串 t 中出现字符的频率,也就是窗口内需要匹配的字符和相应的频率
int[] map = new int[128];
for (char c : t.toCharArray()) {
map[c]++;
}
int left = 0, right = 0;
int match = 0; // 匹配字符的个数
int minLen = s.length() + 1; // 最大的子串的长度
// 子串的起始位置 子串结束的位置(如果不存在这样的子串的话,start,end 都是 0,s.substring 截取就是 “”
int start = 0, end = 0;
while (right < s.length()){
char charRight = s.charAt(right); // 右边界的那个字符
map[charRight]--; // 可以理解为需要匹配的字符 charRight 减少了一个
// 如果字符 charRight 在 t 中存在,那么经过这一次操作,只要个数大于等于 0,说明匹配了一个
// 若字符 charRight 不在 t 中,那么 map[charRight] < 0, 不进行任何操作
if (map[charRight] >= 0) match++;
right++; // 右边界右移,这样下面就变成了 [),方便计算窗口大小
// 只要窗口内匹配的字符达到了要求,右边界固定,左边界收缩
while (match == t.length()){
int size = right - left;
if (size < minLen){
minLen = size;
start = left;
end = right;
}
char charLeft = s.charAt(left); // 左边的那个字符
map[charLeft]++; // 左边的字符要移出窗口
// 不在 t 中出现的字符,移出窗口,最终能够达到的最大值 map[charLeft] = 0
// 如果恰好移出了需要匹配的一个字符,那么这里 map[charLeft] > 0, 也就是还要匹配字符 charLeft,此时 match--
if (map[charLeft] > 0) match--;
left++; // 左边界收缩
}
}
return s.substring(start, end);
}
}
public class Main {
public static void main(String[] args) {
String s = "ADOBECODEBANC";
String t = "ABC";
Solution solution = new Solution();
String res = solution.minWindow(s, t);
System.out.println("结果:" + res);
}
}