Find All Anagrams in a String
Table of Contents + β
Find All Anagrams in a String is a classic fixed-window question. You hunt for every spot in a big string where a small word appears, just shuffled around. The simple way checks each spot from scratch. The interviewer wants to see if you can slide a window and reuse your work instead. That is the real test here.
π― The Problem
You get a long string s and a short string p. Here are the rules.
- Find every starting position in
swhere a substring is an anagram ofp. - An anagram is the same letters in any order. So
abc,bcaandcabare anagrams of each other. - An anagram uses the exact same letters the exact same number of times.
- The matching substring always has the same length as
p. - Return the list of those start positions.
Take s as cbaebabacd and p as abc. The substring at position 0 is cba, the letters a, b, c. That is an anagram, so 0 goes in. The substring at position 6 is bac, same letters again, so 6 goes in too.
Input: s = "cbaebabacd", p = "abc"Output: [0, 6]
Explanation:Substring at index 0 is "cba", an anagram of "abc"Substring at index 6 is "bac", an anagram of "abc"Here is the idea as a picture. A window of the same length as p slides across s one step at a time. At each stop we ask if the letters inside match the letters of p.
The window width never changes. It always equals the length of p.
π’ Approach 1: Sort Every Window (Brute Force)
The idea in one line: slide a window the size of p, then sort each window and compare it to a sorted p.
The idea:
- Take a window the size of
p. - Slide it one step at a time across
s. - Sort the window letters and sort
p. If the sorted forms match, it is an anagram.
How it works:
- At each stop, sort the window.
- Compare it with the pre-sorted
p. - Record the start position when they match.
Why it is weak:
- You sort the window again at every position.
- Sorting
kletters costs aboutk log kwork each time. - The total is about O(n times k log k).
- Two neighbour windows share almost all letters, yet you re-sort everything.
Here is the brute-force code for that idea:
def find_anagrams(s, p): answer = [] target = sorted(p) size = len(p) for i in range(len(s) - size + 1): if sorted(s[i:i + size]) == target: answer.append(i) return answerβ‘ Approach 2: Sliding Window With Counts (Best)
The idea in one line: anagrams only need equal letter counts, so count letters and update only the two that change.
The idea:
- Two strings are anagrams when each letter appears the same number of times in both.
- A count array holds, for each letter, how many times it appears.
- Build one count for
pand one for the current window.
How it works:
- As the window slides one step, only two letters change.
- One new letter enters on the right. One old letter leaves on the left.
- Add one for the entering letter. Subtract one for the leaving letter.
- Compare the window count with the
pcount at each stop. Record a match.
Why it is fast:
- No sorting. Just two small updates per step.
- Each letter enters and leaves the window once.
- The whole scan is O(n).
Here is a dry-run of the count as the window slides. The window is length 3 over cbaebabacd, looking for abc.
Steps to Solve
- If
pis longer thans, there can be no anagram. Return an empty answer. - Build a count of every letter in
p. - Build a count for the first window in
s, which is the firstlen(p)letters. - Compare the two counts. If they match, record position
0. - Slide the window one step. Add the new right letter to the count. Remove the old left letter from the count.
- Compare again. If the counts match, record the new start position.
- Keep sliding until the window reaches the end.
This Python version uses collections.Counter, which counts how many times each letter appears.
from collections import Counter
def find_anagrams(s, p): n, k = len(s), len(p) result = [] if k > n: return result need = Counter(p) # letter counts of p win = Counter(s[:k]) # counts of the first window if win == need: result.append(0) for i in range(k, n): win[s[i]] += 1 # add entering letter win[s[i - k]] -= 1 # remove leaving letter if win[s[i - k]] == 0: del win[s[i - k]] # keep the count clean if win == need: result.append(i - k + 1) return result
s = "cbaebabacd"p = "abc"print(find_anagrams(s, p))The output of the above code will be:
[0, 6]Let us walk through the Python version line by line and see why each line is there.
The line if k > n: return result handles the easy case. If the pattern is longer than the string, no window can fit. So there can be no anagram.
The line need = Counter(p) builds the target counts. Counter reads p and gives back how many times each letter appears. This is what every window must match.
The line win = Counter(s[:k]) counts the first window. s[:k] is the first k letters of s. We count them the same way.
The check if win == need compares the two counts. Two Counter objects are equal when they hold the same letters with the same counts. If they match, the first window is an anagram, so result.append(0) records position 0.
The loop for i in range(k, n) slides the window. Position i is the new letter entering on the right.
The line win[s[i]] += 1 adds the entering letter to the window count. The line win[s[i - k]] -= 1 removes the letter that just left on the left. These two small updates replace rebuilding the whole count.
The lines if win[s[i - k]] == 0: del win[s[i - k]] keep the count clean. When a letterβs count drops to zero we delete its slot. This matters because Counter equality treats a missing letter and a zero count differently. So we remove the empty slot to keep the compare correct.
The check if win == need compares again after the slide. If they match, result.append(i - k + 1) records the start position of this window.
β±οΈ Time and Space Complexity
The brute force sorts each window, so it is slow but uses little extra memory. The sliding window does two updates per step and compares small fixed-size counts. So it runs in one pass. That takes the time from O(n times k log k) down to O(n). The counts hold at most 26 letters, so the extra space is O(1).
| Approach | Time Complexity | Space Complexity |
|---|---|---|
| Brute force (sort each window) | O(n Γ k log k) | O(k) |
| Sliding window with counts | O(n) | O(1) |
Tip
In an interview, say the sort-each-window idea first. Then explain that anagrams only need equal letter counts, not sorting. Show how sliding updates only two letters per step. That reuse of work is the insight the interviewer wants.
π§© Key Takeaways
- β Two strings are anagrams when each letter appears the same number of times in both.
- β The brute force sorts every window, which costs O(n times k log k) time.
- β Counting letters is faster than sorting, and the counts fit in a fixed array of 26 slots.
- β As the window slides, only two letters change, so you update the count instead of rebuilding it.
- β The whole scan is O(n) time and O(1) extra space.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
What does the Find All Anagrams problem ask you to return?
Why: You return the start indices in s where a length-of-p substring is an anagram of p.
- 2
When are two strings anagrams of each other?
Why: Anagrams use the exact same letters the same number of times, just in a different order.
- 3
Why is the sliding window with counts faster than sorting each window?
Why: Neighbour windows share almost all letters, so only the entering and leaving letters change, which means just two count updates per step.
- 4
What is the time and space complexity of the sliding window solution?
Why: One pass with constant work per step is O(n) time, and the 26-slot counts make the extra space O(1).