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