What is LeetCode 438?›
Given strings s and p, return start indices of all p’s anagrams in s. Example: s="cbaebabacd", p="abc" → [0,6].
Master LeetCode 438 with fixed sliding window solutions, multi-language code, edge cases, FAANG strategies.
Authored by Sanjay Patidar, Full-Stack Product Engineer. Portfolio: https://sanjay-patidar.vercel.app
The task in LeetCode 438 is to return all starting indices of the string p
’s anagrams within
string s
. An anagram is a rearrangement of letters where every character of the target
string appears exactly once in the candidate substring.
Official LeetCode 438 Problem Page
This question is a classic example of applying a fixed-size sliding window combined with frequency counting. It evaluates the ability to identify patterns in substring problems, to translate brute force ideas into efficient linear-time algorithms, and to handle overlapping results correctly. The problem is frequently used in technical interviews to test reasoning, edge-case awareness, and clarity of explanation.
Input: s = "cbaebabacd"
, p = "abc"
Output: [0, 6]
The substrings at indices 0 ("cba"
) and 6 ("bac"
) are valid anagrams.
Input: s = "abab"
, p = "ab"
Output: [0, 1, 2]
The substrings "ab"
, "ba"
, and "ab"
all qualify.
Input: s = "abc"
, p = "d"
Output: []
No matching anagrams are present in the string.
Edge Input: s = "a"
, p = "a"
Output: [0]
Single-character input produces a direct match.
1 ≤ s.length, p.length ≤ 3 * 104
Two substrings of equal length are anagrams if their frequency distributions match. By maintaining
a frequency array for p
and another for the current window in s
, and by updating
the window incrementally, the solution can be executed in linear time with constant additional space.
The problem reveals how quickly a candidate recognizes the sliding-window template, avoids off-by-one errors, and reasons about performance under large constraints. It also allows natural follow-up questions, such as handling Unicode input or returning only the count of anagrams instead of the indices.
Choose language and approach to view code and notes. Copy raw code via button.
This problem falls under the sliding window and frequency counting pattern.
Key insight: all anagrams of p
have the same character counts.
By maintaining a constant-size window in s
, we can check for matches in O(1) time per step.
p.length
.toString()
or loop over 26 elements.Input: s="cbaebabacd", p="abc"
Window (s[i:i+m]) | Window Count | p Count | Match? | Action |
---|---|---|---|---|
cba (0-2) | a:1,b:1,c:1 | a:1,b:1,c:1 | Yes | Add 0 |
bae (1-3) | a:1,b:1,e:1 | a:1,b:1,c:1 | No | - |
eba (2-4) | a:1,b:1,e:1 | a:1,b:1,c:1 | No | - |
bac (6-8) | a:1,b:1,c:1 | a:1,b:1,c:1 | Yes | Add 6 |
Pattern Insight | Importance | Action / Tip |
---|---|---|
Sliding Window | Reduces redundant computation | Maintain fixed-size window and update counts incrementally |
Frequency Counting | Detects anagrams efficiently | Use array[26] for lowercase, hashmap for Unicode |
Edge Cases | Prevents runtime errors in interview | Check p.length > s.length, duplicates, single chars, empty strings |
Incremental Updates | Key for O(n) solution | Add right char, remove left char per step |
Compare Counts Efficiently | Critical to avoid false negatives | Use toString() or loop over array instead of sorting |
By following this structured, recruiter-oriented approach, you convey deep **problem understanding, pattern insight, and systematic solution strategy**, which aligns naturally with your other content files.
“A naive way is to sort every substring of length p and compare to sorted p, but that's O(n·m log m). Optimally, use a fixed-size sliding window: track frequency counts for p and current window. Slide the window, update counts in O(1), and when they match, add the start index. This is O(n) time. Example: s = "cbaebabacd", p = "abc" → indices [0,6].”
“Start with brute force: for each start i, take s[i:i+m], sort and compare to sorted p. Then optimize to sliding window: precompute p's freq array. Initialize window freq for first m chars. For each step, add right char, remove left, compare freqs. If equal, record i. Use array[26] for lowercase efficiency.”
“For Find All Anagrams in a String, brute force checks each substring by sorting or hashing, O(n·m log m) or O(n·m). The optimal uses a fixed sliding window of size p.length with two freq arrays (26 for lowercase). Slide: increment right, decrement left, compare arrays in O(26). Collect starts when match. O(n) time, O(1) space. Return [] if none.”
In FAANG, stress: Constant-time freq compare via arrays or counters. Variations: Uppercase? Unicode? (Switch to hashmap). Related: If variable size, like LC76 Minimum Window Substring.
Mastering LeetCode 438 for FAANG requires structured thinking: restate, brute force, optimize, analyze, and dry run. Emphasize pattern recognition in sliding window and frequency counting problems.
Clarify inputs and outputs: "Find start indices of all anagrams of p
in s
. Anagram = same characters and counts. Example: s='cbaebabacd', p='abc'
→ [0,6]. Lowercase only?"
Recruiter Insight: Shows comprehension, asks clarifying constraints.
Check all substrings: sort each s[i:i+m]
and compare to sorted p
. Time O(n·m log m).
Recruiter Insight: Always show brute force first, even if inefficient — demonstrates problem-solving approach.
Use fixed-size window of length m = p.length
with two frequency arrays of size 26.
pFreq
and windowFreq
for first m
chars+s[r]
, -s[l]
, l++
windowFreq == pFreq
, add l
to resultStep | Window | WindowFreq | Match? |
---|---|---|---|
Init | cba | [1,1,1,...] | Yes → add 0 |
Slide 1 | bae | [1,1,0,1,...] | No |
Slide 6 | bac | [1,1,1,...] | Yes → add 6 |
Recruiter Insight: Emphasize incremental updates and O(1) comparisons; shows mastery of sliding window and frequency counting.
Recruiter Insight: Always articulate time-space trade-offs and justify array vs hashmap choice.
s = "cbaebabacd", p = "abc"
pFreq = [1,1,1,...] (a,b,c)
Window "cba": match → add 0
Slide → "bae": no match
Continue → "bac" at 6: match → add 6
Return [0,6]
Recruiter Insight: Stepwise dry runs show clarity of thought and help spot edge cases early.
Pitfall | Impact | Fix / Recruiter Tip |
---|---|---|
No early return if p > s | Index errors / unnecessary computation | Always check if(m>n) return []; |
Off-by-one in indices | Wrong start indices in output | Use i-m+1 after matching window |
Ignoring duplicates | Incorrect matches when letters repeat | Use frequency arrays for exact counts |
Freq comparison errors | Array.equals() fails in JS | Compare with toString() or loop |
The optimal solution uses a fixed-size sliding window over s
with window length
m = p.length
. Two frequency representations are maintained: one for the target string
p
and one for the current window in s
. The window is initialized on the first
m
characters and then moved right by one position repeatedly; updates are incremental
(add the new right character, remove the old left character). For the lowercase-English constraint,
fixed-size arrays of length 26 give the best practical performance.
m > n
(where n = s.length
), immediately return an empty array.pCount[26]
for p
.windowCount[26]
with the first m
characters of s
(if any).0
.i
from m
to n-1
:
s[i]
(new right).s[i - m]
(removed left).i - m + 1
.
FUNCTION findAnagrams(s, p):
n ← length(s)
m ← length(p)
IF m > n:
RETURN []
pCount ← new int[26] // all zeros
wCount ← new int[26] // all zeros
FOR i FROM 0 TO m-1:
pCount[ ord(p[i]) - ord('a') ] += 1
wCount[ ord(s[i]) - ord('a') ] += 1
results ← []
IF pCount == wCount:
results.append(0)
FOR i FROM m TO n-1:
wCount[ ord(s[i]) - ord('a') ] += 1
wCount[ ord(s[i-m]) - ord('a') ] -= 1
IF pCount == wCount:
results.append(i - m + 1)
RETURN results
Rather than comparing two arrays element-by-element on every slide, maintain a matches
counter equal to the number of character positions (0..25) for which pCount[c] === wCount[c]
.
Update matches
only for characters that changed during a slide. When matches === 26
,
the window is an anagram. This reduces per-step work to a few integer comparisons and increments/decrements,
and it is often faster in practice because it avoids scanning all 26 entries every time.
The invariant is: matches equals the count of indices c
in [0..25]
where
pCount[c] == wCount[c]
. When a single character's count changes, only that character's equality
relation can change, so updating matches locally preserves the invariant. Equality of full arrays
is equivalent to matches === 26
.
// Returns array of start indices where p's anagrams begin in s
function findAnagrams(s, p) {
const n = s.length, m = p.length;
if (m > n) return [];
const pCount = new Array(26).fill(0);
const wCount = new Array(26).fill(0);
for (let i = 0; i < m; i++) {
pCount[p.charCodeAt(i) - 97]++;
wCount[s.charCodeAt(i) - 97]++;
}
let matches = 0;
for (let i = 0; i < 26; i++) if (pCount[i] === wCount[i]) matches++;
const res = [];
if (matches === 26) res.push(0);
for (let i = m; i < n; i++) {
const r = s.charCodeAt(i) - 97; // new right char
const l = s.charCodeAt(i - m) - 97; // removed left char
// update right char
if (wCount[r] === pCount[r]) matches--;
wCount[r]++;
if (wCount[r] === pCount[r]) matches++;
// update left char
if (wCount[l] === pCount[l]) matches--;
wCount[l]--;
if (wCount[l] === pCount[l]) matches++;
if (matches === 26) res.push(i - m + 1);
}
return res;
}
Initialization sets the window counts to the first m
characters and builds the target frequency.
The loop invariant after each iteration is: windowCount represents counts of the current window,
and matches (if used) accurately reflects equality positions between arrays. Sliding updates
maintain the correct counts by incrementing the new right character and decrementing the removed left character.
Since every possible window of length m
is considered exactly once, and the detection condition
is exact frequency equality, the algorithm returns all valid starting indices.
Time complexity is O(n) where each character of s
is processed a constant number of times.
For fixed alphabets the per-step work is O(1) (26 is a constant). Space complexity is O(1) for fixed-size
arrays or O(u) if a hashmap is used (where u
is the number of distinct characters).
charCodeAt(i) - 97
. Validate character set
assumptions when constraints change.
i - m + 1
(where i
is the right pointer). Initial window corresponds to index 0.
Arrays.equals
in Java, SequenceEqual
in C#),
or maintain a matches
counter to detect full equality in O(1).
s
(or streamed input), use a fixed-size queue or circular buffer
to manage the window and update counts as bytes arrive. Maintain the same logic to emit indices (or counts) online.
Include tests that validate correctness and edge behavior:
s = "aaaa", p = "aa"
→ overlapping matches.s = "a", p = "aa"
→ []
.The array-based sliding-window is the preferred approach when the alphabet is small and fixed. When confronted with arbitrary or very large alphabets, or when memory is constrained, adapt the representation to hashmaps, and document the tradeoffs during an interview.
Exploring the problem's nuances builds intuition for sliding window and frequency counting patterns.
Anagrams have identical sorted forms or freq maps. Brute sorts each window; optimize by sliding, updating freq incrementally—add right, remove left—for O(1) checks.
Time: O(n), single pass, O(1) per step (26 comparisons).
Space: O(1), fixed arrays. Hashmap: O(m) worst.
Pitfall | Why It Happens | Fix / Tip |
---|---|---|
Incorrect Window Initialization | Not filling initial window or starting slide incorrectly | Initialize window freq for first m chars, then slide from index m |
Frequency Comparison Failure | Using array.equals() fails in JS, or comparing unsorted arrays | Use toString() or loop over array for comparison |
Off-by-One Indices | Adding wrong start index after slide | Add i - m + 1 when freqs match; carefully manage l/r pointers |
Handling Duplicates Incorrectly | Using sets ignores char counts | Always use frequency arrays/maps to capture repeated chars |
Empty or Short Strings | Access out-of-bounds if p.length > s.length | Return early: if (m > n) return [] |
Incremental updates with recruiter-friendly reasoning:
// Optimized with proper initialization & comparison
function findAnagrams(s, p) {
const n = s.length, m = p.length;
if (m > n) return []; // Pitfall: early return
const pCount = new Array(26).fill(0);
const wCount = new Array(26).fill(0);
// Initialize frequency counts
for (let i = 0; i < m; i++) {
pCount[p.charCodeAt(i) - 97]++;
wCount[s.charCodeAt(i) - 97]++;
}
const res = [];
if (pCount.toString() === wCount.toString()) res.push(0);
// Slide window
for (let i = m; i < n; i++) {
wCount[s.charCodeAt(i) - 97]++; // Add new char
wCount[s.charCodeAt(i - m) - 97]--; // Remove old char
if (pCount.toString() === wCount.toString()) res.push(i - m + 1);
}
return res;
}
Scenario | Example | Expected Output | Recruiter Insight |
---|---|---|---|
p.length > s.length | s="ab", p="abc" | [] | Show early return handling |
Empty p | s="abc", p="" | All indices 0 to n-1 | Discuss constraints vs allowed input |
s == p | s="abc", p="abc" | [0] | Basic match, edge start index 0 |
No anagrams | s="xyz", p="abc" | [] | Illustrate handling non-overlapping strings |
All duplicates | s="aaaa", p="aa" | [0,1,2] | Overlapping windows correctly counted |
Single character match | s="a", p="a" | [0] | Minimal length case |
Overlapping anagrams | s="abab", p="ab" | [0,1,2] | Highlight sliding window handling |
End-of-string match | s="abc", p="bc" | [1] | Check right boundary logic |
Identical characters | s="bbb", p="b" | [0,1,2] | Shows correctness with repeated chars |
Optimized sliding window achieves efficiency for large inputs.
O(n): Initialize O(m), slide O(n-m) steps, each O(1) update + O(26) compare. Effective O(n) since 26 constant.
O(1): Fixed arrays[26]. Hashmap variant O(m) for unique chars in p.
Approach | Time | Space | When to Use |
---|---|---|---|
Brute Sort | O(n m log m) | O(m) | Small n,m; simple prototype. |
Brute Hash | O(n m) | O(m) | Better than sort, still quadratic. |
Optimized Window | O(n) | O(1) | Large constraints; production. |
Bottlenecks: Frequent comparisons; optimize with array equality or counter diff tracking.
Implementation details for the fixed sliding window solution vary by language. Select to view optimizations.
Given strings s and p, return start indices of all p’s anagrams in s. Example: s="cbaebabacd", p="abc" → [0,6].
Use a fixed-size sliding window with frequency arrays. Slide window, update counts incrementally, and compare to p’s count at each step.
Time: O(n) — initialize window O(m), slide O(n-m), compare O(26)
Space: O(1) fixed arrays for lowercase chars (or O(m) hashmap for Unicode)
p > s, overlapping anagrams, duplicates, single character, empty strings, end-of-string matches.
Restate problem → brute force → optimize with sliding window → explain time/space → dry run → edge cases → variations/related problems.
Keep window size constant, incrementally update frequency counts for O(1) operations per step, linear scan ensures O(n) efficiency.
Window initialization, frequency comparison, index off-by-one, no early return for p > s, duplicate handling.
Use a hashmap instead of fixed-size array. Time still O(n), space O(unique chars).
LC567 (Permutation in String), LC76 (Minimum Window Substring), LC3, LC49, LC242, LC30 — practice sliding window and frequency counting variations.