接上文滑窗基础题:滑动窗口算法 - LC3 无重复字符的最长子串-CSDN博客,介绍了滑窗的基础题目和滑窗解法模板,这次带来滑窗进阶题解。
76. 最小覆盖子串
困难
给你一个字符串 s
、一个字符串 t
。返回 s
中涵盖 t
所有字符的最小子串。如果 s
中不存在涵盖 t
所有字符的子串,则返回空字符串 ""
。
这道进阶题其实不算难,但细节拉满,漏掉一处都无法通过,我将解题的关键点都注释在代码中。
func minWindow(s string, t string) string {need := make(map[byte]int)window := make(map[byte]int)for i := 0; i < len(t); i++ {need[t[i]]++}valid := 0 //用来记录window中寻找到到满足覆盖t中的字符的个数start, end := 0, math.MaxInt32 //初始化找到的覆盖子串的左右指针l, r := 0, 0 // 初始化左右指针for r < len(s) {//滑窗模板 先动右指针c := s[r]r++if need[c] > 0 { //这个if是为了只处理t中出现的字符串,减少运算,也可省去window[c]++if window[c] == need[c] {valid++ //如果c字符在两个map里出现次数相同,那么说明合法的覆盖字符又多个一个}}//达到滑窗收缩的条件,valid为算法记录的合法覆盖字符数量,len(need)为所有需要覆盖的字符数量for valid == len(need) {//收缩前,先取当前覆盖子串的长度,更新最小长度if r-l < end-start {start = lend = r}//滑窗模板 移动左指针d := s[l]l++if need[d] > 0 { //这个if是为了只处理t中出现的字符串,减少运算,也可省去if window[d] == need[d] {valid-- //左指针即将向后移动,字符d的合法覆盖将不再满足,valid--}window[d]--}}}if end == math.MaxInt32 {return ""}return s[start:end]
}
这里有个点可能会有人有疑问,因为字符串切片是包头不包尾的,即end所在的index是不会被截取的,为什么覆盖子串字符串的是s[start:end]而不是s[start:end+1]?
这是因为我们在外层更新右指针r的for循环里,每次进入循环前先直接更新了右指针r++,也就是说r已经被提前推到下一个索引位置了。假设把r++放到整个for循环的最后,那么end=r就可以改成end=r+1,like this
for r < len(s) {//滑窗模板 先动右指针c := s[r]if need[c] > 0 { //这个if是为了只处理t中出现的字符串,减少运算,也可省去window[c]++if window[c] == need[c] {valid++ //如果c字符在两个map里出现次数相同,那么说明合法的覆盖字符又多个一个}}//达到滑窗收缩的条件,valid为算法记录的合法覆盖字符数量,len(need)为所有需要覆盖的字符数量for valid == len(need) {//收缩前,先取当前覆盖子串的长度,更新最小长度if r-l < end-start {start = lend = r + 1}//滑窗模板 移动左指针d := s[l]l++if need[d] > 0 { //这个if是为了只处理t中出现的字符串,减少运算,也可省去if window[d] == need[d] {valid-- //左指针即将向后移动,字符d的合法覆盖将不再满足,valid--}window[d]--}}r++}