LeetCode76 最小覆盖子串

69 阅读2分钟

leetcode.cn/problems/mi…

image.png

解法一:滑动窗口

  1. 我们在字符串 S 中使用双指针中的左右指针技巧,初始化 left = right = 0,把索引左闭右开区间 [left, right) 称为一个「窗口」。初始没有元素,right向右走一步就扩大一位,包含一个元素了
  2. 先不断地增加 right 指针扩大窗口 [left, right),直到窗口中的字符串符合要求(包含了 T 中的所有字符)。即找到了一个覆盖子串。
  3. 此时,我们停止增加 right,转而不断增加 left 指针缩小窗口 [left, right),直到窗口中的字符串不再符合要求(不包含 T 中的所有字符了)。同时,每次增加 left,我们都要更新一轮结果,即更新覆盖子串的最小长度。
  4. 重复第 2 和第 3 步,直到 right 到达字符串 S 的尽头。

第 2 步相当于在寻找一个「可行解」,然后第 3 步在优化这个「可行解」,最终找到最优解,也就是最短的覆盖子串。左右指针轮流前进,窗口大小增增减减,就好像一条毛毛虫,一伸一缩,不断向右滑动,这就是「滑动窗口」这个名字的来历。

func minWindow(s string, t string) string {
    window := make(map[rune]int)
    need := make(map[rune]int)
    for _, c := range t{
        need[c]++
    }

    left, right := 0, 0
    res := ""
    minStrLen := math.MaxInt // 最小覆盖子串长度,初始为最大值
    matchCount := 0
    
    // 如果一个字符进入窗口,应该增加 `window` 计数器;
    // 如果一个字符将移出窗口的时候,应该减少 `window` 计数器;
    // 当 `matchCount` 满足 `need` 时应该收缩窗口;应该在收缩窗口的时候更新最终结果。
    
    for right < len(s){
        // 新进入窗口的字符
        c := rune(s[right])
        // 扩大窗口
        right++

        if _, ok := need[c]; ok{
            window[c]++
            if window[c] == need[c]{
                matchCount++
            }
        }
        // 判断左侧窗口是否要收缩
        for matchCount == len(need){ // 找到一个覆盖子串
            // 更新答案
            if right - left < minStrLen{
                res = s[left:right]
                minStrLen = right - left
            }
            // 移除窗口左边界,缩小窗口
            dc := rune(s[left])
            left++
            // 进行窗口内的更新
            if _, ok := need[dc]; ok{
                if window[dc] == need[dc]{
                    matchCount--
                }
                window[dc]--
            }
        }
    }
    return res
}