力扣Hot100题目解析:76. 最小覆盖子串(Java详解)
一、题目描述
给你两个字符串 s 和 t ,要求你在 s 中找出包含 t 所有字符的最小子串(要求所有字符都要包含,且顺序不限,字符可以重复)。如果不存在这样的子串,返回空字符串。
示例:
输入: s = "ADOBECODEBANC", t = "ABC"
输出: "BANC"
二、解题思路(滑动窗口+哈希表)
本题属于典型的滑动窗口问题,结合哈希表统计字符出现次数。
步骤解析:
- 用两个哈希表:
need
统计 t 中每个字符需要的数量。window
统计当前窗口中每个字符的数量。
- 用两个指针
left
和right
表示当前滑动窗口的左右边界,初始都为 0。 - 扩大右边界(
right
),把字符加入window
,更新窗口计数。 - 当窗口内已包含 t 所需的全部字符(用一个变量
valid
记录满足条件的字符种类数):- 缩小左边界(
left
),尽量让窗口变小,直到不再满足条件。 - 过程中不断更新最小覆盖子串的位置和长度。
- 缩小左边界(
- 最后返回记录的最小子串即可。
这种方法时间复杂度为 O(n),空间复杂度为 O(字符种类数)。
三、Java代码实现(详细注释)
import java.util.HashMap;
public class Solution {
public String minWindow(String s, String t) {
// 统计t中的字符需求
HashMap<Character, Integer> need = new HashMap<>();
HashMap<Character, Integer> window = new HashMap<>();
for (char c : t.toCharArray()) {
need.put(c, need.getOrDefault(c, 0) + 1);
}
int left = 0, right = 0; // 滑动窗口
int valid = 0; // 当前窗口满足需求的字符种类数
// 记录最小覆盖子串的起始索引和长度
int start = 0, len = Integer.MAX_VALUE;
while (right < s.length()) {
char c = s.charAt(right);
right++;
// 当前字符在t中需要
if (need.containsKey(c)) {
window.put(c, window.getOrDefault(c, 0) + 1);
// 如果窗口中该字符数量和need中的一样,说明满足了这个字符
if (window.get(c).intValue() == need.get(c).intValue()) {
valid++;
}
}
// 当所有需求字符都满足时,尝试收缩左边界
while (valid == need.size()) {
// 更新最小长度及起始位置
if (right - left < len) {
start = left;
len = right - left;
}
char d = s.charAt(left);
left++;
if (need.containsKey(d)) {
if (window.get(d).intValue() == need.get(d).intValue()) {
valid--;
}
window.put(d, window.get(d) - 1);
}
}
}
// 没有找到则返回空字符串
return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len);
}
}
代码详解:
need
记录t中每个字符需要出现的次数。window
记录当前窗口内每个字符的出现次数。valid
统计满足需求的字符种类数。- 每次右移窗口,更新window;当满足全部需求时,尝试收缩左边界,更新最小子串。
- 返回记录的最小子串。
四、总结
本题通过滑动窗口+哈希表的经典组合,高效解决了最小覆盖子串问题。掌握这种方法能帮助你应对各种子串匹配和窗口类问题。