Find All Anagrams in a String
Given two strings s and p, return an array of all the start indices of p's anagrams in s. You may return the answer in any order. An anagram is a word or phrase formed by rearranging the letters of a different word using all the original letters exactly once.
- 1 <= s.length, p.length <= 3 * 10^4
- s and p consist of lowercase English letters only
s = "cbaebabacd", p = "abc"[0, 6]s = "abab", p = "ab"[0, 1, 2]s = "aa", p = "bb"[]This is the same job as Permutation in String, with one change in the goal. There, we asked "does s contain any rearrangement of p?" and could stop at the first yes. Here we must collect the start index of every window in s that is a rearrangement of p — so instead of returning on the first match, we record its position and keep gliding to the end.
Everything that made permutation-checking work carries over unchanged. Two strings are anagrams exactly when their letter counts match, so we never sort or generate rearrangements — we compare tallies.
Let m = len(p). The naive plan walks every window of length m and rebuilds its letter count from scratch to compare against p.
from collections import Counter
def find_anagrams_naive(s, p):
n, m = len(s), len(p)
target = Counter(p)
res = []
for i in range(n - m + 1):
if Counter(s[i:i+m]) == target: # recounts m letters every window
res.append(i)
return resThat is O(N × m) — the exact overlap-wasting we fixed in Maximum Sum Subarray and Permutation in String. Neighbouring windows differ by only one letter out and one letter in, so recounting the whole window each time is throwing the shared middle away.
Carry a 26-letter tally of the current window, patch it one-in / one-out as the window slides, and each time the tally equals p's tally, append the window's start index i - m + 1. No early return — we want them all.
from collections import Counter
def find_anagrams(s, p):
n, m = len(s), len(p)
if m > n: return []
target = Counter(p)
window = Counter(s[:m]) # first window, built once
res = []
if window == target: res.append(0)
for i in range(m, n):
window[s[i]] += 1 # incoming letter
window[s[i - m]] -= 1 # outgoing letter
if window[s[i - m]] == 0:
del window[s[i - m]] # drop bare zeros so the tallies compare equal
if window == target:
res.append(i - m + 1) # start index of THIS window
return resi - m + 1, the window's left edge — not i, which is its right edge. When the incoming letter sits at i, the window spans i - m + 1 .. i, and the answer wants where the anagram starts.Windows can overlap — in s = "abab", p = "ab" the answer is [0, 1, 2] — which is fine, because the tally never resets; it just keeps sliding one letter at a time.
Nothing about the pattern changed from Permutation in String — same fixed width len(p), same 26-letter tally as state, same +1/−1 fold. Only slot (d), what we record, is different: instead of "return true on the first match" it is "append the start index on every match, then carry on." That is the useful lesson — when a problem shifts from "is there one?" to "find all of them", the sliding window itself does not change; you only stop returning early and start collecting. The recognition signals are identical: anagram / permutation wording (so, letter counts), a fixed target length, and a long text to scan.
Find All Anagrams
Frequency Matching
Anagrams are just character frequency matches. Instead of generating permutations, we use a frequency map of 26 characters to check for equality in O(1).
Step-by-Step Logic
Step 1: Initial window 'cba'. All characters {a, b, c} are present. Found index 0!