个人主页:五敷有你
系列专栏:算法分析与设计
⛺️稳中求进,晒太阳
给你一个字符串
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 的子串中,因此没有符合条件的子字符串,返回空字符串。
用哈希表+滑动窗口
初始化变量和数据结构:
- 定义变量
n1
和n2
分别表示字符串s
和t
的长度。- 初始化空字符串
minStr
用于存储最小子串。- 初始化变量
left
为0,表示窗口的左边界。- 使用
HashMap
数据结构map
存储字符串t
中每个字符及其出现次数。统计字符串
t
中字符的出现次数:
- 遍历字符串
t
中的每个字符,将字符及其出现次数存储在map
中。遍历字符串
s
中的字符:
- 使用变量
count
统计当前窗口中包含字符串t
中字符的数量。- 遍历字符串
s
中的字符,如果字符在t
中,更新map
中对应字符的出现次数,并根据出现次数是否大于等于零更新count
。- 当
count
等于n2
时,表示当前窗口包含了字符串t
中的所有字符,进入内部的while
循环。滑动窗口:
- 在
while
循环中,首先更新最小子串minStr
,如果当前子串比之前的子串更短或者minStr
为空,则更新。- 然后移动窗口的左边界
left
,通过逐步增加左边界的位置,缩小窗口,直到不能满足包含所有字符的条件。- 继续遍历字符串
s
中的字符,重复上述步骤,直到遍历完成。返回最小子串:
- 返回最终的最小子串
minStr
。这个算法通过滑动窗口的方式,优化了暴力搜索的复杂度,使得算法在时间上更为高效
class Solution {
public String minWindow(String s, String t) {
int n1 = s.length();
int n2 = t.length();
String minStr = "";
int left = 0;
Map map = new HashMap<>();
for (int i = 0; i < n2; i++) {
map.put(t.charAt(i), map.getOrDefault(t.charAt(i), 0) + 1);
}
int count = 0;
for (int j = 0; j < n1; j++) {
if (map.containsKey(s.charAt(j))) {
map.put(s.charAt(j), map.get(s.charAt(j)) - 1);
if (map.get(s.charAt(j)) >= 0) {
count++;
}
while (count == n2) {
if (minStr.equals("") || j - left + 1 < minStr.length()) {
minStr = s.substring(left, j + 1);
}
if (map.containsKey(s.charAt(left))) {
map.put(s.charAt(left), map.get(s.charAt(left)) + 1);
if (map.get(s.charAt(left)) > 0) {
count--;
}
}
left++;
}
}
}
return minStr;
}
}
时间复杂度为
其中 n2 为字符串
t
的长度。内部有一个while
循环,但由于每个字符最多只会进入窗口两次(一次进入,一次出去),整体的时间复杂度仍然是 O(n1),其中 n1 为字符串s
的长度。空间复杂度分析
最坏情况下,
map
中可能存储字符串t
所有不同字符,因此空间复杂度为 O(n2),其中 n2 为字符串t
的长度。