题目
给你一个字符串 S、一个字符串 T 。请你设计一种算法,可以在 O(n) 的时间复杂度内,从字符串 S 里面找出:包含 T 所有字符的最小子串。
分析
遇到子串搜索类型的问题,我们第一想到的一般都是滑动窗口的方法,然后再设定窗口滑动是否要变动,如何变动问问题。
我们可以先分析利用双指针来操作滑动窗口的问题,然后在依次解决算法转化的具体问题。
首先,双指针的关键之处在于左右指针,右指针先行移动,先找到符合条件的最大子串,即最大覆盖子串,然后左指针再移动,找到在该情景下的最小覆盖子串,即此子串为该情况下的最小子串。
然后,右指针继续移动,找寻新的窗口,以求得整个 s 子串中,最小覆盖 t 的子串,具体操作思路如下图:
初始时,左右指针在起始位置,准备移动建造窗口
假设 t 子串为 abc
然后右指针移动,建造窗口,找到最大覆盖子串
然后移动左指针,先找到最小子串,记录然后继续移动
然后右指针继续搜索,左指针继续移动,找到新的子串和之前的对比,直到找到最小覆盖子串
因为我们要将三个不同的字符依次搜索成功,如果用 || 或 && 的逻辑语句来连接,很有可能出现最后存在有三个或者多个相同字符,所以建议可以直接直接使用 map 搜索,快速简答还可以避免重复。
关于算法的具体操作,我们需要创建一个窗口,一个容纳 t 字符串,左右指针,窗口长度和窗口起始指针,还有一个参数记录已被搜索的字样,前提摆好,代码如下,时间复杂度为 O(n2):
func minWindow(s string, t string) string {
//先行创建窗口和搜索框
need, window := map[byte]int{}, map[byte]int{}
for i := range t {
need[t[i]]++
}
//检索是否该字符被搜索过,记录长度和起始位置
valid := 0
index, length := 0, math.MaxInt32
//设定左右指针
left, right := 0, 0
//设定循环长度,当右指针搜索到最后一字符时停止
for right < len(s) {
tempAdd := s[right]
right++
//利用map判断是否符合,存在有符合字段,窗口移动创建
if _, ok := need[tempAdd]; ok {
window[tempAdd]++
//判断搜索到几位
if window[tempAdd] == need[tempAdd] {
valid++
}
}
//设定搜索范围
for valid == len(need) {
if right-left < length {
index = left
length = right - left
}
tempDel := s[left]
left++
//同样的,判断是否符合条件
if _, ok := need[tempDel]; ok {
window[tempDel]--
//若不符合覆盖条件,则退出
if window[tempDel] < need[tempDel] {
valid--
}
}
}
}
//若无符合条件,则返回空
if length == math.MaxInt32 {
return ""
}
//若有,返回最大覆盖子串
return s[index : index+length]
}