AlgoMaster Logo

Find All Anagrams in a String

Last Updated: April 2, 2026

medium
4 min read

Understanding the Problem

We need to find every position in s where a contiguous substring of length p.length() is an anagram of p. Two strings are anagrams if they contain exactly the same characters with the same frequencies, just in a different order.

The key insight is that we are always looking at substrings of a fixed length (the length of p). This means the window size never changes, which makes this a perfect candidate for a fixed-size sliding window. The challenge is figuring out how to efficiently check whether the current window is an anagram of p without doing expensive work at every position.

Key Constraints:

  • 1 <= s.length, p.length <= 3 * 10^4 → With n up to 30,000, an O(n k) approach (where k is the length of p) is fine, but O(n k log k) sorting at every position is borderline. An O(n) solution is ideal.
  • s and p consist of lowercase English letters → Only 26 possible characters. This means we can use a fixed-size array of 26 instead of a hash map, keeping space constant.

Approach 1: Brute Force (Sorting)

Intuition

The simplest way to check if two strings are anagrams is to sort them both and see if the sorted versions are identical. So for every possible substring of s with length equal to p, we can extract it, sort it, and compare it with the sorted version of p.

This is the approach most people think of first. It works, but sorting each substring is expensive.

Algorithm

  1. Sort p to get sortedP.
  2. For each index i from 0 to s.length() - p.length():
    1. Extract the substring s[i..i+p.length()).
    2. Sort this substring.
    3. If it equals sortedP, add i to the result list.
  3. Return the result list.

Code

The bottleneck here is sorting every substring from scratch. Each window overlaps with the previous one by k - 1 characters, but we throw all that information away and re-sort. What if we could update the window's character information incrementally instead of recomputing it?

Approach 2: Frequency Map Comparison

Intuition

Instead of sorting, we can represent each string by its character frequency count. Two strings are anagrams if and only if their frequency counts are identical. We build a frequency map for p once, then slide a fixed-size window across s, maintaining a frequency map for the current window. When the window slides right by one position, we add the new character and remove the old one, then compare the two maps.

The comparison itself takes O(26) = O(1) time since there are only 26 lowercase letters. This is already much better than sorting.

Algorithm

  1. Build a frequency array pCount of size 26 for p.
  2. Build a frequency array windowCount of size 26 for the first window s[0..p.length()).
  3. If windowCount equals pCount, add index 0 to the result.
  4. Slide the window from index 1 to s.length() - p.length():
    1. Add the new character entering the window (increment its count).
    2. Remove the character leaving the window (decrement its count).
    3. Compare windowCount with pCount. If they match, add the current start index.
  5. Return the result list.

Code

This approach is already O(n), but at every window position we compare all 26 entries of the two frequency arrays. What if we tracked how many characters already match so we could skip the full comparison and just update the match count incrementally?

Approach 3: Sliding Window with Match Count (Optimal)

Intuition

Building on the frequency map idea, we can avoid comparing all 26 entries at every step. The trick is to maintain a matches counter that tracks how many of the 26 characters currently have the same frequency in both the window and p.

When the window slides, at most two character frequencies change (the one entering and the one leaving). So we only need to check those two characters against their target frequency and update matches accordingly. When matches reaches 26, every character frequency lines up and we have an anagram.

This is a classic optimization pattern: instead of recomputing a global property from scratch, maintain it incrementally by handling only what changed.

Algorithm

  1. Build frequency arrays pCount and windowCount for p and the first window.
  2. Initialize matches by counting how many indices (0 to 25) have pCount[i] == windowCount[i].
  3. Slide the window one position at a time:
    1. Add the new character c_in. Increment windowCount[c_in]. If this made the counts equal, increment matches. If this broke a previous match, decrement matches.
    2. Remove the old character c_out. Decrement windowCount[c_out]. Apply the same match logic.
    3. If matches == 26, add the current start index to the result.
  4. Return the result list.

Code