Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
76. 最小覆盖子串
题目描述
-
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。 注意:
-
对于
t
中重复字符,我们寻找的子字符串中该字符数量必须不少于t
中该字符数量。 -
如果
s
中存在这样的子串,我们保证它是唯一的答案。 -
1 <= s.length, t.length <= 105
-
s
和t
由英文字母组成 示例 1:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
示例 2:
输入:s = "a", t = "a"
输出:"a"
示例 3:
输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。
思路分析
- 这也是
leetcode
的一道hard
题目,仔细阅读题目后发现这并不难想到思路,而是难在如何处理细节。如果在这使用暴力法的话,显然不符合题目的考察意向,所以要用其他方法。 - 当遇到一个字符串是否是另一个字符串的子串时,我们一般都会想到用滑动窗口。但是本题得难点在于用什么数据结构来维护滑动窗口。为了降低代码得操作复杂性,我们可以用哈希表来维护滑动得窗口,同时为了保证程序得性能,所以用数组来模拟哈希表,具体见 AC 代码。易知,当 长度为 , 长度为 时,时间复杂度为 。
AC 代码
class Solution {
public String minWindow(String s, String t) {
char[] cs = s.toCharArray();
char[] ct = t.toCharArray();
int n = cs.length;
// 如果 s 的长度小于 t 的长度,那么 s 中肯定不存在涵盖 t 所有字符的子串.
if (n < ct.length) return "";
// 统计 t 字符串的字符.
int[] tMap = new int['z' - 'A' + 1];
// 统计滑动窗口的字符,用于匹配 t 字符串.
int[] sMap = new int['z' - 'A' + 1];
// 记录当前在窗口内是否还欠缺某个字符才使得窗口字符匹配 t 字符.
Set<Integer> set = new HashSet<>();
for (char c : ct) {
int index = c - 'A';
set.add(index);
tMap[index]++;
}
String res = "";
// 记录窗口得最小长度.
int min = 100000;
// 窗口左边界.
int left = 0;
for (int i = 0; i < n; i++) {
int index = cs[i] - 'A';
sMap[index]++;
if (sMap[index] >= tMap[index]) {
set.remove(index);
}
if (set.size() != 0) continue;
// 在窗口包含 t 字符串时,是否能继续缩短字符长度.
while (left < i && (tMap[cs[left] - 'A'] == 0 ||
(tMap[cs[left] - 'A'] != 0 && sMap[cs[left] - 'A'] > tMap[cs[left] - 'A']))) {
sMap[cs[left++] - 'A']--;
}
if (i - left + 1 < min) {
res = s.substring(left,i + 1);
min = i - left + 1;
}
set.add(cs[left] - 'A');
sMap[cs[left++] - 'A']--;
}
return res;
}
}
总结
- 滑动窗口题型虽然很常见,但是遇到它得各种变种题时还是要多加注意的,不然的话就很容易栽倒在细节坑上,平时在练习时也要多用几种数据结构去解决问题,才能够应付更多的变型题。