【LeetCode】76. 最小覆盖子串

leetcode

 

解题思路

该问题需要找到字符串 s 中包含 t 所有字符的最小子串,且需满足字符出现次数的要求。核心解决方法是 ​​滑动窗口​​ 配合 ​​哈希表​​ 或 ​​数组​​ 统计字符频率,通过动态调整窗口边界实现高效搜索。

 

关键步骤

  1. ​​初始化统计​​:统计 t 中字符的出现次数,存入哈希表或数组。
  2. ​​滑动窗口维护​​:
    • ​​右指针扩展窗口​​:逐个将字符加入窗口,更新统计信息。
    • ​​左指针收缩窗口​​:当窗口满足条件时,尝试缩小窗口以寻找最小覆盖子串。
  3. ​​条件判断​​:通过比较窗口内字符频率与 t 的频率,确定是否满足要求。
  4. ​​更新结果​​:记录最小窗口的起始位置和长度。

复杂度分析

  • ​​时间复杂度​​: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]
}

代码注释

  1. ​​初始化统计​​:用 tFreq 存储 t 的字符频率,windowFreq 统计窗口内字符。
  2. ​​滑动窗口逻辑​​:
    • ​​右指针扩展​​:将字符加入窗口,若其频率与 t 匹配,则 valid 增加。
    • ​​左指针收缩​​:当 valid 等于 t 的字符种类数时,尝试缩小窗口并记录最小值。
  3. ​​边界处理​​:若未找到有效窗口,返回空字符串;否则返回子串。

运行示例

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 只有一个,返回空。
posted @ 2025-04-25 11:38  云隙之间  阅读(66)  评论(0)    收藏  举报