外观
最小覆盖子串
⭐ 题目日期:
百度 - 2024 9月底
🧗难度系数:
⭐️ ⭐️ ⭐️ ⭐️
🌳 题目描述:
给你一个字符串 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 的子串中,
因此没有符合条件的子字符串,返回空字符串。
📝思路分析:
滑动窗口:
- 左右指针,左指针指向0,移动右指针找到第一个匹配状态。
- 完成匹配后,移动左指针,缩小窗口的大小,直到匹配状态被破坏,同时更新结果。
- 匹配失衡后,继续移动右指针,寻找下一个匹配状态,重复步骤1, 2, 3
💻代码:
Java
public String minWindow(String s, String t) {
if (s == null || t == null || s.length() < t.length()) {
return "";
}
// 统计字符串 t 中每个字符的出现次数
Map<Character, Integer> targetMap = new HashMap<>();
for (int i = 0; i < t.length(); i++) {
char c = t.charAt(i);
targetMap.put(c, targetMap.getOrDefault(c, 0) + 1);
}
// 滑动窗口中的字符出现次数
Map<Character, Integer> windowMap = new HashMap<>();
int left = 0; // 窗口左边界
int minLength = Integer.MAX_VALUE; // 最小窗口长度
int count = 0; // 当前窗口中已经满足的字符个数
int minLeft = 0; // 最小窗口的起始位置
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (targetMap.containsKey(c)) {
windowMap.put(c, windowMap.getOrDefault(c, 0) + 1);
// 如果当前字符的出现次数不超过目标字符串中的该字符次数,则计数器加一
if (windowMap.get(c).intValue() <= targetMap.get(c).intValue()) {
count++;
}
}
// 当窗口中包含了所有目标字符,尝试缩小窗口
while (count == t.length()) {
if (i - left + 1 < minLength) {
minLength = i - left + 1;
minLeft = left;
}
char leftChar = s.charAt(left);
if (targetMap.containsKey(leftChar)) {
windowMap.put(leftChar, windowMap.get(leftChar) - 1);
// 如果当前字符的出现次数小于目标字符串中的该字符次数,则计数器减一
if (windowMap.get(leftChar).intValue() < targetMap.get(leftChar).intValue()) {
count--;
}
}
left++; // 移动窗口左边界
}
}
// 如果未找到符合条件的窗口,返回空字符串
if (minLength > s.length()) {
return "";
}
// 返回最小覆盖子串
return s.substring(minLeft, minLeft + minLength);
}
🚵🏻复杂度分析:
时间复杂度:O(m + n), m 为 s 的长度,n 为 t 的长度,t 中的元素访问一遍,s中的元素至多访问两遍
空间复杂度:O(1), map中存储的entry数量为字符的种类,输入只包含英文字母,故为O(1)