题目描述
思路解析动画文字版
每个起点都切出长 k 的子串、排好序和 p 比,能做,但每个窗口都重头排序,重复劳动太多。
窗口长度固定等于 len(p)。先数出 p 的字母计数,再让窗口在 s 上滑:每右移一格,右边新进一个字符、左边挤出一个字符,计数 O(1) 更新。窗口计数和 p 计数一致,起点就是答案。计数相等就是异位词(顺序无所谓),所以根本不用排序。
准备:先数 p="ab":a 一个、b 一个。窗口固定 2 格,将从最左滑到最右,每停一处就比一次计数。
建首窗口 · 进 s[0]=a:先把前 k=2 个字符装进首窗口。第一个吃进 a,计数 a=1,窗口还差一格、先不比。
窗口 [0,1] = "ab":第一个窗口 "ab",计数和 p 一模一样——是异位词!记下起点 0。ans=[0]。
右滑 → 窗口 [1,2]:窗口整体右移一格:右端吃进 s[2]=b,左端挤出 s[0]=a。这就是定长滑窗的核心——一进一出,不用重数。现在窗口是 "bb"。
窗口 [1,2] = "bb":"bb" 的计数是 a 零个、b 两个,b 多了一个、a 少了一个,和 p 对不上——不是异位词,跳过不记录。这就是反例:计数不等就直接放过,窗口照样往右挤。
右滑 → 窗口 [2,3]:继续右移:右端吃进 s[3]=a,左端挤出 s[1]=b。同样只改了两个计数。窗口变成 "ba"。
窗口 [2,3] = "ba":"ba" 计数 a 一个 b 一个,又和 p 相等——是异位词!记下起点 2。顺序虽和 p 不同,但计数相等就算数。ans=[0, 2]。
灵魂帧慢放 · 一进一出:把这关键一步放慢看:窗口右移,先把离开的 s[1]=b 减一,再把新进的 s[3]=a 加一。其余 24 个字母的计数纹丝不动——这就是滑窗每步只花 O(1) 的原因。
结束:窗口右端到头,再也滑不动了。一路收集到的起点是 [0, 2],就是答案。
凡是「固定长度子串/子数组,问满足某条件的有几处」都是这个套路:窗口右滑时同步加右、减左,用计数 O(1) 维护,绝不每个窗口重头算。
迁移阶梯:练熟「定长窗口 + 比计数」后,去同类题迁移:LC567 只把「收集所有起点」改成「存不存在」;再上一层到变长窗口 LC76,合法条件从「计数完全相等」放宽成「覆盖 t」。也可以问问 AI 助教「定长窗口和变长窗口的判定条件差在哪」。
边界三连:三个极端:p 长于 s 直接空;全程不匹配也是空;p 含重复字母时,靠计数保留「a 要两个」才不会误判。
面试追问:面试三连的核心:用计数代替排序拿到 O(n)、win==need 是常数开销、扩到大字符集时小心哈希表的残留 0 键。
参考代码
class Solution: def findAnagrams(self, s, p): if len(p) > len(s): return [] need = [0] * 26 win = [0] * 26 for ch in p: need[ord(ch) - 97] += 1 ans = [] for i, ch in enumerate(s): win[ord(ch) - 97] += 1 # 右进 if i >= len(p): win[ord(s[i - len(p)]) - 97] -= 1 # 左出 if win == need: ans.append(i - len(p) + 1) return ans复杂度
- 时间复杂度:O(n),每个字符进窗口、出窗口各一次;每步比一次定长 26 计数是常数
- 空间复杂度:O(1),need 和 win 各是固定 26 长度数组,与输入规模无关
易错点
面试追问把动画讲成自己的话
追问为什么不每个窗口排序再比,而要维护计数?
追问win == need 比的是什么,开销多大?
追问字符集不止小写字母怎么办?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。再去主线里挑下一道练手。
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题