最小覆盖子串 · 最短通关小队
在队伍长廊 s 里找最短连续窗口,使 临时小队至少满足任务清单 t 的字符频次(顺序无关)。
"ADOBECODEBANC"
"ABC"
目标:最短窗口 = "BANC"(顺序 B-A-C,不是 ABC)
R 招人:右扩直到 missing==0(窗口 valid); L 裁员:在仍 valid 时尽量左缩,刷新最短纪录。看频次,不看顺序。
候选队伍长廊 · s
窗口 [0, —]A×1 B×1 C×1
(空)
任务:最短通关小队
s 是候选队伍长廊,t 是任务清单。要在 s 里找一段最短连续窗口,使窗口内至少包含 t 要求的每一种字符、且数量够。
s = "ADOBECODEBANC",t = "ABC" → 需要 A×1、B×1、C×1。目标:最短连续子串覆盖全部任务。
这是「最小覆盖子串」:覆盖看频次,不看顺序。
先统计 t 里每个字符还要多少个 → need 表;missing = 还欠几个字符实例
1func minWindow(s string, t string) string {2 if len(t) == 0 || len(s) == 0 || len(t) > len(s) {3 return ""4 }5 need := make(map[byte]int)6 for i := 0; i < len(t); i++ {7 need[t[i]]++8 }9 missing := len(t)10 left := 011 start, minLen := 0, len(s)+11213 for right := 0; right < len(s); right++ {14 c := s[right]15 if need[c] > 0 {16 missing--17 }18 need[c]--1920 for missing == 0 {21 if right-left+1 < minLen {22 start = left23 minLen = right - left + 124 }25 lc := s[left]26 need[lc]++27 if need[lc] > 0 {28 missing++29 }30 left++31 }32 }33 if minLen == len(s)+1 {34 return ""35 }36 return s[start : start+minLen]37}
行级中文解释
L14, L15
need[c] > 0 表示这个字符还欠着,读到它可以还债,missing--
L16
need[c]-- 表示字符进入窗口;哪怕是多余的也要记录(会变成负数)
L18
missing == 0 表示窗口已经覆盖 t 的全部频次
L24, L25
收缩左边时 need[lc]++;若变回正数,说明移走了必要实例,窗口失效
L25, L26
need[lc] > 0 时 missing++,停止继续收缩
常见误区
只比「有没有」不比「有几个」
t="AAB" 需要 A×2;窗口 "AAAB" 里虽有 A、B 两种字符,但 A 只出现 1 次仍不 valid。
误以为要找连续子串 "ABC"
答案 "BANC" 的顺序是 B-A-C;覆盖看频次,不看排列顺序。
valid 后立刻停
第一次 valid 往往很长;要在 valid 后继续缩 L,才能拿到最短窗口。
步骤一览
面试口述模板
我会用变长滑动窗口。先用 need 记录 t 中每个字符还需要多少个,missing 表示还差多少个字符实例。右指针不断扩张窗口,每读到一个还欠着的字符,就让 missing--。当 missing==0 时,说明窗口已经覆盖 t,此时不断移动左指针收缩窗口,并更新最短答案。如果移走某个字符导致 need 变回正数,说明窗口失效,停止收缩。每个字符最多进窗口一次、出窗口一次,所以时间复杂度 O(n)。
迁移练习
- LC3 无重复最长子串(变长滑窗,但不变量是「无重复」)
- LC438 找所有异位词(定长滑窗 + 频次匹配)
- LC567 字符串排列(同 LC438 思路)