Find all anagrams in a string is a classic problem that appears frequently in coding interviews, competitive programming, and real‑world text‑processing tasks. The goal is to locate every substring of a given text that is an anagram of a pattern string — meaning the substring contains exactly the same characters with the same frequencies, possibly in a different order. Solving this efficiently requires a blend of string manipulation, frequency counting, and sliding‑window techniques. Below you’ll find a thorough walk‑through that explains the concept, outlines several algorithmic strategies, provides step‑by‑step pseudocode, analyzes time and space complexity, and discusses practical applications Not complicated — just consistent..
Introduction
When you find all anagrams in a string, you are essentially scanning a larger body of text for every possible rearrangement of a smaller pattern. To give you an idea, given the text "cbaebabacd" and the pattern "abc", the anagrams appear at indices 0 ("cba"), 6 ("bac"), and 7 ("acd" is not an anagram, sorry—actually index 6 gives "bac" and index 0 gives "cba"; the correct indices are 0 and 6). Mastering this problem sharpens your ability to work with character frequency arrays, hash maps, and linear‑time scanning—skills that translate directly to tasks like DNA sequence analysis, plagiarism detection, and spell‑checking utilities.
Understanding Anagrams
An anagram is a word or phrase formed by rearranging the letters of another, using each original letter exactly once. In algorithmic terms, two strings s1 and s2 are anagrams iff:
- They have the same length.
- For every character
c, the count ofcins1equals the count ofcins2.
Thus, checking anagrams reduces to comparing frequency distributions rather than the actual ordering of characters.
Problem Statement
Input
- A text string
Tof lengthn. - A pattern string
Pof lengthm(m ≤ n).
Output
- A list of all starting indices
i(0‑based) such that the substringT[i … i+m‑1]is an anagram ofP.
If no such substring exists, return an empty list Simple, but easy to overlook..
Approaches to Solve the Problem
1. Brute‑Force Check
The simplest method enumerates every possible window of length m in T, sorts the characters inside the window, and compares the sorted window to the sorted pattern.
Steps
- Pre‑compute
sortedP = sort(P). - For each index
ifrom0ton‑m:- Extract
window = T[i:i+m]. - If
sort(window) == sortedP, recordi.
- Extract
Complexity
- Time:
O((n‑m+1) * m log m)due to sorting each window. - Space:
O(m)for the temporary sorted window.
While easy to understand, this approach becomes impractical for large inputs because of the repeated sorting overhead.
2. Frequency‑Map Comparison (Naïve Sliding Window)
Instead of sorting, we can compare character frequency maps for each window.
Steps
- Build a frequency array/map
freqPfor the pattern (size = 26for lowercase English letters, or a hash map for general Unicode). - For each window start
i:- Build a fresh frequency map
freqWindowforT[i:i+m]. - If
freqWindow == freqP, addito the answer.
- Build a fresh frequency map
Complexity
- Time:
O((n‑m+1) * m)because we rebuild the map from scratch each time. - Space:
O(Σ)where Σ is the size of the character set (constant for ASCII).
This improves over brute force but still scans m characters per window.
3. Optimized Sliding Window with Incremental Updates
The optimal solution maintains a rolling frequency map as the window slides one character to the right, updating counts in O(1) per step And that's really what it comes down to..
Core Idea
- Keep a frequency diff array
diff[256](or hash map) wherediff[c] = count_in_window(c) - count_in_pattern(c). - The window is an anagram iff all entries in
diffare zero. - Maintain a counter
zeroCountthat tracks how many entries are currently zero; whenzeroCount == Σ, we have a match.
Algorithm
function findAnagrams(T, P):
n ← length(T), m ← length(P)
if m > n: return empty list
// 1. Build frequency of pattern
freqP[256] ← 0
for ch in P:
freqP[ch] ← freqP[ch] + 1
// 2. Initialize window frequencies and diff
diff[256] ← 0
zeroCount ← 0
for ch in alphabet: // initialize diff as -freqP
diff[ch] ← -freqP[ch]
if diff[ch] == 0: zeroCount ← zeroCount + 1
// 3. Add first m characters of T to the window
for i from 0 to m‑1:
ch ← T[i]
if diff[ch] == 0: zeroCount ← zeroCount - 1 // will become non‑zero
diff[ch] ← diff[ch] + 1
if diff[ch] == 0: zeroCount ← zeroCount + 1 // became zero again
result ← []
if zeroCount == 256: result.append(0)
// 4. Slide the window
for i from m to n‑1:
outCh ← T[i‑m] // character leaving the window
inCh ← T[i] // character entering the window
// remove outCh
if diff[outCh] == 0: zeroCount ← zeroCount - 1
diff[outCh] ← diff[outCh] - 1
if diff[outCh] == 0: zeroCount ← zeroCount + 1
// add inCh
if diff[inCh] == 0: zeroCount ← zeroCount - 1
diff[inCh] ← diff[inCh] + 1
if diff[inCh] == 0: zeroCount ← zeroCount + 1
if zeroCount == 256:
result.append(i‑m+1)
return result