最小覆盖子串
题目描述:
给你一个字符串 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 的子串中, 因此没有符合条件的子字符串,返回空字符串。
提示:
m == s.length
n == t.length
1 <= m, n <= 105
s
和t
由英文字母组成
思路分析:
本题依旧可以使用滑动窗口的方法,具体思路如下:
- 使用两个指针
l
(左指针)和r
(右指针)来定义一个滑动窗口,该窗口表示当前正在考虑的s
的子串。 debt
变量表示还需要找到多少个t
中的字符才能使窗口内的字符集与t
相同。初始时,debt
等于t
的长度,因为我们还没有找到任何字符。- 移动右指针
r
,扩展窗口,直到debt
变为 0,表示窗口内的字符集已经包含了t
的所有字符。 - 当
debt
为 0 时,尝试移动左指针l
,缩小窗口,直到窗口不再包含t
的所有字符(即ant[s1[l]]
大于 0),同时记录过程中遇到的最小窗口大小和起始位置。 - 重复上述步骤,直到右指针
r
遍历完整个s
代码实现注解:
class Solution {
public String minWindow(String s, String t) {
// 如果s的长度小于t的长度,那么s中不可能包含t,直接返回空字符串
if (s.length() < t.length()) {
return "";
}
// 将字符串s和t转换为字符数组
char[] s1 = s.toCharArray();
char[] t1 = t.toCharArray();
// 创建一个长度为256的数组ant,用于记录t中每个字符还需要多少个才能满足条件(初始化为负数表示还需要)
int[] ant = new int[256];
for (char c : t1) {
ant[c]--; // 对于t中的每个字符,都标记为还需要一个(因为初始化为负数,所以减一)
}
int res = Integer.MAX_VALUE; // 用于记录最短子串的长度,初始化为最大值
int start = 0; // 记录最短子串的起始位置
for (int l = 0, r = 0, debt = t.length(); r < s.length(); r++) {
// 每当右指针指向的字符在t中出现过,就减少对应的debt(表示还需要的字符数)
if (ant[s1[r]]++ < 0) {
debt--;
}
// 当debt为0时,表示窗口内已经包含了t中的所有字符
if (debt == 0) {
// 尝试缩小窗口的左边界,直到窗口内不再包含多余的t中的字符
while (ant[s1[l]] > 0) {
ant[s1[l++]]--;
}
// 更新最短子串的长度和起始位置
if (r - l + 1 < res) {
res = r - l + 1;
start = l;
}
}
}
// 如果res没有被更新过(即没有找到满足条件的子串),则返回空字符串
return res == Integer.MAX_VALUE ? "" : s.substring(start, start + res);
}
}