🌈个人首页: 神马都会亿点点的毛毛张
1.题目描述
给你一个字符串 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 < = 1 0 5 1 <= m, n <= 10^5 1<=m,n<=105
s
和t
由英文字母组成
进阶: 你能设计一个在 o(m+n)
时间内解决此问题的算法吗?
2.题解
2.1 暴力解法(超时)
import java.util.HashMap;
import java.util.Map;
class Solution {
public String minWindow(String s, String t) {
// 创建Map集合用于记录t字符串中的字母及其个数
Map<Character, Integer> map = new HashMap<>();
for (int i = 0; i < t.length(); i++) {
char c = t.charAt(i);
map.put(c, map.getOrDefault(c, 0) + 1);
}
// 初始化最小长度为一个较大的值
int minLen = Integer.MAX_VALUE;
// 初始化左右指针
int left = 0, right = 0;
// 用于记录当前窗口的长度
int length = 0;
// 用于记录最终结果的子串
String result = "";
// 遍历s字符串
for (left = 0; left < s.length(); left++) {
// 每次左指针移动时都重新创建一个countMap,复制map
Map<Character, Integer> countMap = new HashMap<>(map);
// 从当前left位置开始移动右指针
for (right = left; right < s.length(); right++) {
char c = s.charAt(right);
// 如果右指针位置的字符在countMap中,减少它的计数
if (countMap.containsKey(c)) {
countMap.put(c, countMap.get(c) - 1);
// 如果计数减少到0,移除该字符
if (countMap.get(c) == 0) {
countMap.remove(c);
}
}
// 当countMap为空时,说明当前窗口包含了t中的所有字符
if (countMap.size() == 0) {
// 计算当前窗口的长度
length = right - left + 1;
// 更新最小长度和结果字符串
if (length < minLen) {
minLen = length;
result = s.substring(left, right + 1);
}
break;
}
}
}
return result;
}
}
2.2 双指针-哈希集合
import java.util.HashMap;
import java.util.Map;
class Solution {
// 创建Map集合用于记录t字符串中的字母及其个数
Map<Character, Integer> tMap = new HashMap<>();
// 创建Map集合用于记录s字符串滑动窗口过程中的字母及其个数
Map<Character, Integer> sMap = new HashMap<>();
/**
* 找到字符串s中包含字符串t所有字符的最小子串
*
* @param s 字符串s
* @param t 字符串t
* @return 包含字符串t所有字符的最小子串
*/
public String minWindow(String s, String t) {
// 初始化tMap,用于记录t字符串中的每个字符及其出现次数
for (int i = 0; i < t.length(); i++) {
Character c = t.charAt(i);
tMap.put(c, tMap.getOrDefault(c, 0) + 1);
}
// 初始化最小长度和结果字符串
int minLen = Integer.MAX_VALUE;
String result = "";
// 滑动窗口的左右指针
int left, right;
// 遍历s字符串
for (left = 0, right = 0; right < s.length(); right++) {
// 获取当前字符
Character c = s.charAt(right);
// 将当前字符加入sMap中,记录其出现次数
sMap.put(c, sMap.getOrDefault(c, 0) + 1);
// 当滑动窗口中包含t字符串的所有字符时,收缩左边界
while (check()) {
// 更新最小长度和结果字符串
int length = right - left + 1;
if (length < minLen) {
minLen = length;
result = s.substring(left, right + 1);
}
// 移除左边界的字符,并更新sMap中的计数
c = s.charAt(left);
sMap.put(c, sMap.get(c) - 1);
// 收缩左边界
left++;
}
}
// 返回最小窗口的子串
return result;
}
/**
* 检查当前滑动窗口是否包含t字符串中的所有字符
*
* @return 如果包含所有字符,返回true;否则返回false
*/
public boolean check() {
// 遍历tMap中的每个字符及其计数
for (Map.Entry<Character, Integer> entry : tMap.entrySet()) {
Character c = entry.getKey();
// 如果sMap中当前字符的计数小于tMap中的计数,返回false
if (sMap.getOrDefault(c, 0) < entry.getValue()) {
return false;
}
}
// 如果sMap中包含tMap中的所有字符及其计数,返回true
return true;
}
}
2.3 双指针-哈希数组
- 如果能用哈希集合,那么大概率也可以使用哈希数组
class Solution {
// 主方法,寻找字符串 s 中包含字符串 t 的最小窗口子串
public String minWindow(String s, String t) {
// 用于存储 t 中各字符的出现次数
int[] tArr = new int[128];
// 用于存储 s 中当前窗口各字符的出现次数
int[] sArr = new int[128];
// 初始化 tArr 数组
for (int i = 0; i < t.length(); i++) {
tArr[t.charAt(i)]++;
}
int left = 0, right = 0; // 左右指针初始化为 0
int minLen = Integer.MAX_VALUE; // 最小长度初始化为无穷大
String result = ""; // 最终结果字符串初始化为空
// 移动右指针,扩展窗口
while (right < s.length()) {
sArr[s.charAt(right)]++; // 记录右指针指向字符的出现次数
// 当窗口内的字符已经包含 t 中所有字符时,移动左指针缩小窗口
while (check(sArr, tArr)) {
int length = right - left + 1; // 计算当前窗口长度
if (length < minLen) { // 如果当前窗口长度小于最小长度,更新最小长度和结果字符串
minLen = length;
result = s.substring(left, right + 1);
}
sArr[s.charAt(left)]--; // 左指针右移前,减少左指针指向字符的出现次数
left++; // 左指针右移,缩小窗口
}
right++; // 右指针右移,扩大窗口
}
return result; // 返回结果字符串
}
// 检查当前窗口是否包含 t 中所有字符
public boolean check(int[] sArr, int[] tArr) {
// 检查大写字母 A-Z
for (int i = 'A'; i <= 'Z'; i++) {
if (sArr[i] < tArr[i]) {
return false;
}
}
// 检查小写字母 a-z
for (int i = 'a'; i <= 'z'; i++) {
if (sArr[i] < tArr[i]) {
return false;
}
}
return true; // 当前窗口包含 t 中所有字符
}
}
2.4 优化
- 上面两种方法,每次都需要调用
check()
函数,我们能不能优化一下呢?
class Solution {
// 主方法,寻找字符串 s 中包含字符串 t 的最小窗口子串
public String minWindow(String s, String t) {
// 用于存储 t 中各字符的出现次数
int[] tArr = new int[128];
// 用于存储 s 中当前窗口各字符的出现次数
int[] sArr = new int[128];
// 初始化 tArr 数组,记录 t 中每个字符的出现次数,并统计不同字符的种类数
int cnt = 0;
for (int i = 0; i < t.length(); i++) {
if (tArr[t.charAt(i)]++ == 0) {
cnt++;
}
}
int left = 0, right = 0; // 左右指针初始化为 0
int minLen = Integer.MAX_VALUE; // 最小长度初始化为无穷大
String result = ""; // 最终结果字符串初始化为空
// 移动右指针,扩展窗口
while (right < s.length()) {
char c = s.charAt(right);
sArr[c]++;
// 当窗口中的某字符数量达到 t 中该字符的数量时,计数器 cnt 减 1
if (sArr[c] == tArr[c]) {
cnt--;
}
// 当计数器 cnt 为 0 时,窗口包含了 t 中所有字符
while (cnt == 0) {
int length = right - left + 1;
// 更新最小长度和结果子字符串
if (length < minLen) {
minLen = length;
result = s.substring(left, right + 1);
}
// 移动左指针,缩小窗口
char x = s.charAt(left);
sArr[x]--;
// 当某字符数量小于 t 中该字符数量时,计数器 cnt 加 1
if (sArr[x] < tArr[x]) {
cnt++;
}
left++;
}
right++;
}
return result; // 返回结果字符串
}
}