系列名 leetcode hot 100
题目名 最小覆盖子串
描述
给你一个字符串 s
、一个字符串 t
。返回 s
中涵盖 t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 ""
。
注意:
- 对于
t
中重复字符,我们寻找的子字符串中该字符数量必须不少于t
中该字符数量。 - 如果
s
中存在这样的子串,我们保证它是唯一的答案。
解题思路
1、暴力求解,以t的长度作为窗口在s上滑动,判断窗口内的子串是否包含t中所有字符。接着扩大窗口长度知道最大窗口为s的长度。复杂度超过O(n^3)
2、双指针+hash优化,哈希表记录子串中字符频次和窗口中字符频次来确定左右指针是否能拓展和收缩
代码段
// Java
// 双指针+hash
public String minWindowx(String s, String t) {
//维护s串中滑动窗口中各个字符出现次数
Map<Character, Integer> smap = new HashMap<>();
//维护t串中各个字符出现次数
Map<Character, Integer> tmap = new HashMap<>();
for (int i = 0; i < t.length(); i++) {
tmap.put(t.charAt(i), tmap.getOrDefault(t.charAt(i), 0)+1);
}
String res="";
//cnt维护s串[left,right]中满足t串的元素的个数,记录相对应字符的总数 len对应找到的子串的长度
int len=Integer.MAX_VALUE,count=0;
//区间[left,right]表示当前滑动窗口
for (int left=0,right = 0; right < s.length(); right++) {
smap.put(s.charAt(right), smap.getOrDefault(s.charAt(right), 0)+1);
//如果ht表中也包含当前字符
if (tmap.containsKey(s.charAt(right))) {
//并且hs表中的字符个数<=ht表中的字符个数,说明该字符是必须的,并且还未到达字符串t所要求的数量
if (smap.get(s.charAt(right))<=tmap.get(s.charAt(right))) {
count++;
}
}
//收缩滑动窗口
//如果左边界的值不在ht表中 或者 它在hs表中的出现次数多于ht表中的出现次数
while(left < right && (!tmap.containsKey(s.charAt(left)) || smap.get(s.charAt(left)) > tmap.get(s.charAt(left)))){
smap.put(s.charAt(left),smap.get(s.charAt(left)) - 1);
left++;
}
//此时滑动窗口包含符串 t 的全部字符
if (count==t.length()&&right-left+1<len) {
len=right-left+1;
res=s.substring(left,right+1);
}
}
return res;
}
// 优化代码
class Solution {
public String minWindow(String s, String t) {
int[] map = new int[128];
// 遍历字符串 t,初始化每个字母的次数
for (int i = 0; i < t.length(); i++) {
char char_i = t.charAt(i);
map[char_i]++;
}
int left = 0; // 左指针
int right = 0; // 右指针
int ans_left = 0; // 保存最小窗口的左边界
int ans_right = -1; // 保存最小窗口的右边界
int ans_len = Integer.MAX_VALUE; // 当前最小窗口的长度
int count = t.length();
// 遍历字符串 s
while (right < s.length()) {
char char_right = s.charAt(right);
// 当前的字母次数减一
map[char_right]--;
//代表当前符合了一个字母
if (map[char_right] >= 0) {
count--;
}
// 开始移动左指针,减小窗口
while (count == 0) { // 如果当前窗口包含所有字母,就进入循环
// 当前窗口大小
int temp_len = right - left + 1;
// 如果当前窗口更小,则更新相应变量
if (temp_len < ans_len) {
ans_left = left;
ans_right = right;
ans_len = temp_len;
}
// 得到左指针的字母
char key = s.charAt(left);
// 因为要把当前字母移除,所有相应次数要加 1
map[key]++;
//此时的 map[key] 大于 0 了,表示缺少当前字母了,count++
if (map[key] > 0) {
count++;
}
left++; // 左指针右移
}
// 右指针右移扩大窗口
right++;
}
return s.substring(ans_left, ans_right + 1);
}
}
// Kotlin
// Python
// Go
// JavaScript
// C++
// Rust
技巧总结
一般设计子串问题,优先考虑用双指针构造滑动窗口+hash查找法解决,一般字符串如果是全字母且不区分大小写的情形,注意考虑字母数组来统计词频辅助解决。