76. 最小覆盖子串

76. 最小覆盖子串

hard

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 ""

注意:

  • 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
  • 如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:

1
2
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"

示例 2:

1
2
输入:s = "a", t = "a"
输出:"a"

示例 3:

1
2
3
4
输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。

滑动窗口

典型的滑动窗口思路,窗口扩展时寻找可行解,窗口收缩时优化可行解。

image-20220222013719279 image-20220222013827787
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
func minWindow(s string, t string) string {
   if s == t {
      return s
   }
   hash := make(map[byte]int) // 记录目标字母及其个数 作为需求
   for i := 0; i < len(t); i++ {
      hash[t[i]]++
   }
   reL, reR := 0, -1 // 目标的左右边界
   minLen := math.MaxInt
   left, right := 0, 0
   sum := len(t) // 记录总数 为0时代表全命中

   for right < len(s) {
      if _, ok := hash[s[right]]; ok {
         // 存在
         hash[s[right]]--
         if hash[s[right]] >= 0 {
            // 变成负数时sum不用减少 因为已经满足
            sum--
         }
      }

      if sum == 0 {
         // 全部命中
         for left <= right {
            // left右移 直到不满足全命中
            if _, ok := hash[s[left]]; ok {
               hash[s[left]]++
               if hash[s[left]] > 0 {
                  // 需求得不到满足了
                  if minLen > right-left+1 {
                     // 刷新最短值
                     reR, reL = right, left
                     minLen = right - left + 1
                  }
                  sum++
                  left++ // 正式地++ 破坏全命中状态
                  break
               }
            }
            left++ // 注意 先判断再++
         }
      }
      right++
   }
   return s[reL : reR+1]
}