438. Find All Anagrams in a String
Quick Identifier
- Topic: sliding-window
- Pattern: Sliding Window (Fixed Window + Frequency Match)
- Hint: Find anagrams of
pinsides. Anagrams have the exact same character frequencies. Use a fixed-size window of lengthp.lengthand compare frequency maps. - Difficulty: Medium
- Companies: Amazon, Google, Meta, Microsoft, Bloomberg
- LeetCode: 438
Quick Sights
- Time Complexity:
O(n). We initialize the first window in $O(p)$ time, then slide it acrossstaking $O(1)$ per step. Comparing the two size-26 arrays at each step is an $O(26) = O(1)$ operation. - Space Complexity:
O(1). We only need two integer arrays of fixed size 26 (one forp's frequencies, one for the current window's frequencies). - Core Mechanism: Since an anagram must be exactly the same length as
p, our sliding window's size never changes after initialization. For every step rightward, we add the new character to our window's frequency array, and subtract the character that just fell out the left side. If the window's frequency array matchesp's frequency array, we record theleftindex.
Concept Explanation
As a senior engineer, the leap here is understanding that an "anagram" is just a multiset equality problem. The exact sequence of characters doesn't matter; only their counts do.
- The Target Signature: First, scan the target string
p. Build a "signature" of it—a simple size-26 array counting how many times each letter appears. - The Fixed Frame: Because any valid anagram must be exactly
p.lengthcharacters long, we don't need a complex variable window that expands and shrinks unpredictably. We just build a rigid frame of lengthp.length. - The Rolling Hash (Frequency): Place this frame over the start of string
s. Count the letters inside. Does it match the Target Signature? If yes, record the start index. - The Slide: Now, grab the frame and shift it exactly one spot to the right.
- One old letter falls out of the left side (decrement its count).
- One new letter enters the right side (increment its count).
- Check if the signatures match again.
- Repeat until the frame hits the end of
s. This single-step slide ensures we never recalculate the whole frame from scratch.
Diagram
This flowchart illustrates the fixed sliding window mechanics and the $O(1)$ rolling update.
Loading diagram…
Study Pattern (SDE3+)
- 0–3 min: Clarify that $O(n \cdot m \log m)$ sorting per window is naive. Mention that maintaining a running frequency array drops it to $O(n \cdot 26) = O(n)$.
- Implementation pass: Use Go
[26]intequality or a JS helper function to compare the two arrays. Ensure you initialize the very first window before entering the main slide loop. - 5 min extension: How would you optimize the array comparison if the alphabet was all 1M+ Unicode characters? You couldn't compare arrays of size 1M efficiently. You would track a single integer
mismatches(the number of distinct characters whose counts differ). Whenmismatches == 0, you have an anagram.
Approaches
- Brute force — For every index, extract substring of length $m$, sort it, compare to sorted $p$. Time: $O(n \cdot m \log m)$.
- Acceptable — Sliding window with Hash Map. Time: $O(n)$ but Hash Map equality is heavier.
- Optimal — Sliding window with size-26 integer arrays. Go allows direct array comparison
if a == b. JS requires a small loop. Time: $O(n)$. (Pick this)
Optimal Solution
Go
func findAnagrams(s string, p string) []int {
out := []int{}
if len(p) > len(s) {
return out
}
want := [26]int{}
for i := 0; i < len(p); i++ {
want[p[i]-'a']++
}
window := [26]int{}
// Initialize the first window
for i := 0; i < len(p); i++ {
window[s[i]-'a']++
}
// Check the first window
if window == want {
out = append(out, 0)
}
// Slide the window
for right := len(p); right < len(s); right++ {
// Add the new character on the right
window[s[right]-'a']++
// Remove the old character from the left
window[s[right-len(p)]-'a']--
if window == want {
out = append(out, right-len(p)+1)
}
}
return out
}JavaScript
function findAnagrams(s, p) {
const out = [];
if (p.length > s.length) return out;
const want = new Array(26).fill(0);
const window = new Array(26).fill(0);
// Initialize frequencies for the target and the first window
for (let i = 0; i < p.length; i++) {
want[p.charCodeAt(i) - 97]++;
window[s.charCodeAt(i) - 97]++;
}
if (arraysEqual(window, want)) {
out.push(0);
}
// Slide the window
for (let right = p.length; right < s.length; right++) {
// Add the new character
window[s.charCodeAt(right) - 97]++;
// Remove the character that left the window
window[s.charCodeAt(right - p.length) - 97]--;
if (arraysEqual(window, want)) {
// The start index of the current window is right - p.length + 1
out.push(right - p.length + 1);
}
}
return out;
}
function arraysEqual(a, b) {
for (let i = 0; i < 26; i++) {
if (a[i] !== b[i]) return false;
}
return true;
}Walkthrough
Input: s = "cbaebabacd", p = "abc" (Lengths: s = 10, p = 3)
want = [a:1, b:1, c:1]
| right | char | Window Range | substring | window counts | action | out |
|---|---|---|---|---|---|---|
| Initial | - | [0, 2] | "cba" | a:1,b:1,c:1 | Matches want! | [0] |
| 3 | e | [1, 3] | "bae" | Drop c. Add e. | Invalid | [0] |
| 4 | b | [2, 4] | "aeb" | Drop b. Add b. | Invalid | [0] |
| 5 | a | [3, 5] | "eba" | Drop a. Add a. | Invalid | [0] |
| 6 | b | [4, 6] | "bab" | Drop e. Add b. | Invalid | [0] |
| 7 | a | [5, 7] | "aba" | Drop b. Add a. | Invalid | [0] |
| 8 | c | [6, 8] | "bac" | Drop b. Add c. | Matches want! | [0, 6] |
| 9 | d | [7, 9] | "acd" | Drop b. Add d. | Invalid | [0, 6] |
Output: [0, 6]
Edge Cases
pis longer thans(Immediately return empty array).- Multiple overlapping anagrams (e.g.,
s = "aaaa",p = "aa". Should return[0, 1, 2]).
Pitfalls
- Forgetting to check the very first window before entering the sliding loop.
- Off-by-one errors calculating the
leftindex. The character dropping out is at indexright - p.length. The start index of the newly formed valid window isright - p.length + 1.
Similar Problems
- 567. Permutation in String — Exact same logic, but returns a single boolean instead of an array of indices.
- 076. Minimum Window Substring — Variable window where the target is a subset, not an exact length match.
Mind-Map Tags
#sliding-window #fixed-window #anagram #all-indices #frequency-match
Mark this page when you finish learning it.
Last updated on
Spotted something unclear or wrong on this page?