题目描述
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
注意:
- 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
- 如果 s 中存在这样的子串,我们保证它是唯一的答案。
示例 1:
**输入:**s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
**解释:**最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。
示例 2:
**输入:**s = "a", t = "a"
输出:"a"
**解释:**整个字符串 s 是最小覆盖子串。
示例 3:
**输入:**s = "a", t = "aa"
输出: ""
**解释:**t 中两个字符 'a' 均应包含在 s 的子串中, 因此没有符合条件的子字符串,返回空字符串。
提示:
s和t由英文字母组成m == s.lengthn == t.length1 <= m, n <= 105
解题思路
从题目的描述的可以看出,要求的子串在原串中带有起始位置和终止位置的连续字符串,那么,这时可以考虑使用滑动窗口来求解。在本题中实现滑动窗口,需要确定如下三点:
- 窗口内是什么?
- 如何移动窗口的起始位置?
- 如何移动窗口的终止位置?
窗口就是 s 中涵盖 t 所有字符的子串。
窗口的起始位置如何移动:
起始位置移动分析
我们可以发现,当找到这样的子串时,它可能并不是最小的,因为对于 t 中重复字符,我们寻找的子字符串中该字符数量可能多于t 中该字符数量,例如下图这种情况:

这时我们就需要从找到的子串中削减重复字符的数量,直到某个字符的数量少于t中相应字符的数量,那么此时得到的子串就是最小的。
基于此,我们可以得出:当在s中找到符合条件的子串时,起始位置不断向前移动,每移动一次,所指向字符的数量减少1,直到某个字符的数量少于t中相应字符的数量时停止,得到当前最小子串。
窗口的终止位置如何移动:终止就是遍历字符串s的指针。
代码实现
class Solution {
public String minWindow(String s, String t) {
int slen = s.length(), tlen = t.length();
// 存储s和t中不同字符的数量
int[] sc = new int[128];
int[] tc = new int[128];
// count用来判断初始子串是否已找到,tempLen表示上一个最小子串的长度
int count = tlen, tempLen = 1000000;
// 窗口起始与终止指针
int left = 0, right = 0;
// 最终结果
String res = "";
// 统计字符串t中的不同字符数量
for (int i = 0; i < tlen; ++i) {
char c = t.charAt(i);
tc[c]++;
}
for (; right < slen; ++right) {
char c = s.charAt(right);
/**
当s中对应t中相应字符数量相等时,减去该数量。
当count小于或等于0时,满足条件的初始子串已找到,
其中小于0的情况代表t中有重复字符
*/
if (tc[c] == ++sc[c]) count -= tc[c];
if (count <= 0) {
while(left <= right) {
c = s.charAt(left++);
if (tc[c] > --sc[c]){
String ts = s.substring(left-1, right + 1);
if(ts.length()<tempLen){
tempLen=ts.length();
res = ts;
}
break;
}
}
// t中每个字符至少出现一次。
count = 1;
}
}
return res;
}
}