题目描述
给你一个字符串
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 <=
s
和t
由英文字母组成进阶:你能设计一个在
o(m+n)
时间内解决此问题的算法吗?
解题思路
这个问题要求我们在字符串 s
中找到包含字符串 t
所有字符的最小子串。问题的核心是要用滑动窗口的技巧来找到最小的满足条件的子串。
滑动窗口算法步骤:
-
初始化两个计数器:一个用于记录当前窗口中各字符的出现次数
windowCount;
另一个用于记录字符串t
中每个字符所需的出现次数targetCount
。 -
使用两个指针表示滑动窗口:
left
和right
都初始化为 0,表示滑动窗口的左右边界;我们从right
开始遍历字符串s
,并将字符加入窗口中。 -
移动
right
指针扩大窗口:每次移动right
指针,将字符加入windowCount
中;检查当前窗口是否包含字符串t
中所有字符,即检查windowCount
是否满足targetCount
。 -
移动
left
指针缩小窗口:当窗口包含t
中所有字符时,开始移动left
指针以缩小窗口,尽量找到最小的符合条件的子串;在缩小窗口的过程中,不断更新最小子串的长度和起始位置。 -
返回结果:当遍历完成后,返回找到的最小子串,如果没有符合条件的子串,则返回空字符串
""
。
复杂度分析
-
时间复杂度:O(m + n)。其中
m
是字符串s
的长度,n
是字符串t
的长度。我们只需遍历字符串s
一次,并且每个字符在windowCount
中的增减操作是 O(1) 的,因此总时间复杂度为 O(m + n)。 -
空间复杂度:O(∣S∣ + ∣T∣)。需要 O(∣T∣) 的空间存储字符串
t
中每个字符的计数,并且滑动窗口可能需要 O(∣S∣) 的空间来存储窗口中的字符计数。因此总空间复杂度为 O(∣S∣ + ∣T∣)。
代码实现
package org.zyf.javabasic.letcode.hot100.substring;
import java.util.HashMap;
import java.util.Map;
/**
* @program: zyfboot-javabasic
* @description: 最小覆盖子串
* @author: zhangyanfeng
* @create: 2024-08-21 21:55
**/
public class MinWindowSolution {
public String minWindow(String s, String t) {
if (s == null || t == null || s.length() < t.length()) {
return "";
}
// 记录t中每个字符的出现次数
Map<Character, Integer> targetCount = new HashMap<>();
for (char c : t.toCharArray()) {
targetCount.put(c, targetCount.getOrDefault(c, 0) + 1);
}
// 定义窗口计数器
Map<Character, Integer> windowCount = new HashMap<>();
int left = 0, right = 0, matchCount = 0;
int minLen = Integer.MAX_VALUE;
int minStart = 0;
while (right < s.length()) {
char cRight = s.charAt(right);
if (targetCount.containsKey(cRight)) {
windowCount.put(cRight, windowCount.getOrDefault(cRight, 0) + 1);
if (windowCount.get(cRight).intValue() == targetCount.get(cRight).intValue()) {
matchCount++;
}
}
right++;
// 当窗口包含所有t中的字符后,开始收缩窗口
while (matchCount == targetCount.size()) {
// 更新最小窗口
if (right - left < minLen) {
minLen = right - left;
minStart = left;
}
char cLeft = s.charAt(left);
if (targetCount.containsKey(cLeft)) {
windowCount.put(cLeft, windowCount.get(cLeft) - 1);
if (windowCount.get(cLeft) < targetCount.get(cLeft)) {
matchCount--;
}
}
left++;
}
}
return minLen == Integer.MAX_VALUE ? "" : s.substring(minStart, minStart + minLen);
}
public static void main(String[] args) {
MinWindowSolution solution = new MinWindowSolution();
String s1 = "ADOBECODEBANC";
String t1 = "ABC";
System.out.println(solution.minWindow(s1, t1)); // 输出: "BANC"
String s2 = "a";
String t2 = "a";
System.out.println(solution.minWindow(s2, t2)); // 输出: "a"
String s3 = "a";
String t3 = "aa";
System.out.println(solution.minWindow(s3, t3)); // 输出: ""
}
}
具体可参考:https://zyfcodes.blog.csdn.net/article/details/141401712