leetcode
解题思路
该问题需要找到字符串 s 中包含 t 所有字符的最小子串,且需满足字符出现次数的要求。核心解决方法是 滑动窗口 配合 哈希表 或 数组 统计字符频率,通过动态调整窗口边界实现高效搜索。
关键步骤
- 初始化统计:统计
t 中字符的出现次数,存入哈希表或数组。
- 滑动窗口维护:
- 右指针扩展窗口:逐个将字符加入窗口,更新统计信息。
- 左指针收缩窗口:当窗口满足条件时,尝试缩小窗口以寻找最小覆盖子串。
- 条件判断:通过比较窗口内字符频率与
t 的频率,确定是否满足要求。
- 更新结果:记录最小窗口的起始位置和长度。
复杂度分析
- 时间复杂度:O(M + N),其中 M 是
t 的长度,N 是 s 的长度。每个字符最多被左右指针各遍历一次。
- 空间复杂度:O(C),C 为字符集大小(如 ASCII 对应 128),用于存储字符频率。
代码实现
func minWindow(s string, t string) string {
if len(s) == 0 || len(t) == 0 || len(s) < len(t) {
return ""
}
// 统计 t 的字符频率
tFreq := make(map[byte]int)
for i := 0; i < len(t); i++ {
tFreq[t[i]]++
}
windowFreq := make(map[byte]int) // 窗口内字符频率
left, right := 0, 0 // 滑动窗口左右指针
valid := 0 // 满足 t 频率要求的字符种类数
minLen := math.MaxInt32 // 最小窗口长度
start := 0 // 最小窗口起始位置
for right < len(s) {
c := s[right]
right++
// 若当前字符在 t 中,更新窗口统计
if _, exists := tFreq[c]; exists {
windowFreq[c]++
if windowFreq[c] == tFreq[c] {
valid++
}
}
// 当窗口包含所有 t 的字符时,尝试收缩左边界
for valid == len(tFreq) {
// 更新最小窗口
if right - left < minLen {
minLen = right - left
start = left
}
// 移动左指针
d := s[left]
left++
if _, exists := tFreq[d]; exists {
if windowFreq[d] == tFreq[d] {
valid-- // 移除后不再满足条件
}
windowFreq[d]--
}
}
}
if minLen == math.MaxInt32 {
return ""
}
return s[start : start+minLen]
}
代码注释
- 初始化统计:用
tFreq 存储 t 的字符频率,windowFreq 统计窗口内字符。
- 滑动窗口逻辑:
- 右指针扩展:将字符加入窗口,若其频率与
t 匹配,则 valid 增加。
- 左指针收缩:当
valid 等于 t 的字符种类数时,尝试缩小窗口并记录最小值。
- 边界处理:若未找到有效窗口,返回空字符串;否则返回子串。
运行示例
func main() {
// 示例 1
s1 := "ADOBECODEBANC"
t1 := "ABC"
fmt.Println(minWindow(s1, t1)) // 输出: "BANC"
// 示例 2
s2 := "a"
t2 := "a"
fmt.Println(minWindow(s2, t2)) // 输出: "a"
// 示例 3
s3 := "a"
t3 := "aa"
fmt.Println(minWindow(s3, t3)) // 输出: ""
}
示例解析
- 示例 1:窗口逐步扩展至包含 "A", "B", "C",最终在 "BANC" 处找到最小子串。
- 示例 2:单个字符直接匹配。
- 示例 3:
t 要求两个 'a',但 s 只有一个,返回空。