438. Find All Anagrams in a String
Problem Statement: You are given two strings
sandp. Find every position inswhere an anagram ofpbegins, and return those positions in any order.
Input: s = "cbaebabacd", p = "abc"
Output: [0, 6]
Picture it
An anagram of p has exactly the same letters in the same quantities as p, so it also has exactly the same length. That is the detail that shapes the whole solution: the window never changes size.
Lay a window of width len(p) over the start of s, count the letters inside it, and compare those counts with the counts of p. Match means you have found an anagram. Then shove the window one step right and compare again.
The only question is how to keep the window's letter counts up to date without recounting them from scratch each time.
How to think about it
Sliding one step changes exactly two things. One letter enters on the right, and one letter leaves on the left. Everything between them is untouched.
So maintain the counts incrementally: add one for the entering letter, subtract one for the leaving letter. That is two updates per position rather than a fresh count of the whole window, which is the difference between a fast solution and a slow one.
Remove counts that fall to zero. When a letter's count drops to zero, delete the entry rather than leaving a zero behind. A counter holding "zero e" is not necessarily considered equal to one with no e at all, depending on the version of Python, and deleting it keeps the comparison honest everywhere.
The comparison itself looks expensive but is not. Two counters over lowercase letters can differ in at most 26 entries, so each comparison is a fixed amount of work no matter how long the strings are.
If p is longer than s there is nowhere for a window to sit, so return an empty list straight away.
Walking through the example
Take s = "cbaebabacd" and p = "abc", so the window is three wide and the target counts are one each of a, b and c.
- Window
cbaat position0. One each ofa,b,c. That matches, so record position0. - Slide:
eenters,cleaves. The window isbae, which has aneand noc. No match. - Slide:
benters,bleaves. The window isaeb. Still anein there. No match. - The next few slides keep carrying that
eor dropping letters the target needs, so nothing matches. - Window
bacat position6. One each again, so record position6. - The last slides bring in
d, whichpdoes not contain at all, so they fail.
The answer is [0, 6].
The code
from collections import Counter
class Solution:
def findAnagrams(self, s: str, p: str) -> list[int]:
width = len(p)
if width > len(s):
return []
need = Counter(p)
window = Counter(s[:width])
out = [0] if window == need else []
for right in range(width, len(s)):
# one letter in on the right
window[s[right]] += 1
# one letter out on the left
leaving = s[right - width]
window[leaving] -= 1
if window[leaving] == 0:
del window[leaving]
if window == need:
out.append(right - width + 1)
return out
Time and space complexity
Time:
The window slides n times, and each slide is two counter updates plus one comparison. The comparison touches at most 26 letters, which is a constant, so the work per step does not grow with the input.
Recounting the whole window at each position instead would multiply everything by the width of p, which is the version to avoid.
Space:
Two counters, each holding at most one entry per letter of the alphabet. Neither grows with the length of s.
Remember this
An anagram has the same length as the pattern, so the window is fixed width and only ever slides. Update the counts with one letter in and one letter out rather than recounting, and delete counts that reach zero so the comparison stays reliable. time, space.