LeetCode-76. 最小覆盖子串
题目来源
题目详情
给你一个字符串 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 的子串中,
因此没有符合条件的子字符串,返回空字符串。
提示:
1 <= s.length, t.length <= 105
s
和t
由英文字母组成
进阶: 你能设计一个在 o(n)
时间内解决此问题的算法吗?
相似题目
题解分析
解法一:双指针法
滑动窗口的思想:
用i
,j
表示滑动窗口的左边界和右边界,通过改变i
,j
来扩展和收缩滑动窗口,可以想象成一个窗口在字符串上游走,当这个窗口包含的元素满足条件,即包含字符串T的所有元素,记录下这个滑动窗口的长度j-i+1
,这些长度中的最小值就是要求的结果。
步骤一
不断增加j
使滑动窗口增大,直到窗口包含了T的所有元素
步骤二
不断增加i
使滑动窗口缩小,因为是要求最小字串,所以将不必要的元素排除在外,使长度减小,直到碰到一个必须包含的元素,这个时候不能再扔了,再扔就不满足条件了,记录此时滑动窗口的长度,并保存最小值
步骤三
让i
再增加一个位置,这个时候滑动窗口肯定不满足条件了,那么继续从步骤一开始执行,寻找新的满足条件的滑动窗口,如此反复,直到j
超出了字符串S范围。
如何判断滑动窗口包含了T的所有元素?
- 对于如何判断滑动窗口中包含所有元素,我们可以使用一个整数数组,类似于以前做过的字符串所有相同的字母异位词问题和字母异位词分组问题。
- 这里可以优化的一个地方就是,如果单纯使用cnts数组计数每个字符出现的次数,那么在判断的时候每次都需要判断数组中所有元素的数量都小于等于0时,表示当前滑动窗口不再需要任何元素。
- 具体地,我们可以再使用一个计数指针cnt表示目标串中总共需要计数的字符数,并在滑动窗口的过程中维护该变量。最后,只需要判断cnt是否为0即可。
class Solution {
public String minWindow(String s, String t) {
int n = s.length();
int m = t.length();
int[] cnts = new int[128];
int cnt = m;// 表示每个滑动窗口中需要保证t中的所有字符都出现
for(int i=0; i<m; i++) {
cnts[t.charAt(i)]++;// 表示此轮遍历中该字符必须出现的次数
}
String res = "";
int mins = Integer.MAX_VALUE;
// 左闭右闭区间
int left = 0, right = 0;
while(right < n) {
char ch = s.charAt(right);
if (cnts[ch] > 0) {
cnt--;// 找到了t中的一个字符
}
cnts[ch]--;
// 找到了所有t中的字符
if (cnt == 0) {
while (left < right && cnts[s.charAt(left)] < 0) {
// 不断缩小窗口,筛出无关字符
cnts[s.charAt(left)]++;
left++;
}
if (right - left + 1 < mins) {
mins = right - left + 1;
res = s.substring(left, right+1);
}
// 左边界往右收缩一位
cnts[s.charAt(left)]++;
cnt++;
left++;
}
right++;
}
return res;
}
}
结果展示
Either Excellent or Rusty