Skip to content
Reliable Data Engineering
Practice problem medium sliding-windowhash-mapstrings
Practise with timer, notes and rubric

Find All Anagram Positions

Pattern: Sliding Window · Difficulty: Medium · Asked at: Amazon, Meta, Microsoft

Classic version: LeetCode 438

Problem

Given strings s and p, return every start index i such that s[i:i+len(p)] is an anagram (a rearrangement) of p, in increasing order.

Examples

anagram_starts("cbaebabacd", "abc") → [0, 6]
anagram_starts("abab", "ab")        → [0, 1, 2]

Constraints

Starter code

def anagram_starts(s: str, p: str) -> list[int]:
    pass

Hints

Hint 1

The window size is fixed at len(p). Each step adds one character on the right and removes one on the left.

Hint 2

Track how many letters currently have matching counts (matches), updating it only for the two letters that changed. Then the check is O(1).

Where this shows up in data engineering

Fixed windows are everywhere in streaming: tumbling and sliding aggregations, “same multiset of events in each 5-minute window”, detecting a known sequence of operations regardless of order. The rolling-count technique is how stream processors update window aggregates incrementally.

Solution

def anagram_starts(s: str, p: str) -> list[int]:
    m = len(p)
    if m > len(s):
        return []
    need = [0] * 26
    have = [0] * 26
    for ch in p:
        need[ord(ch) - 97] += 1
    res = []
    for i, ch in enumerate(s):
        have[ord(ch) - 97] += 1
        if i >= m:                       # drop the char that left the window
            have[ord(s[i - m]) - 97] -= 1
        if have == need:                 # 26 comparisons: O(1) for a fixed alphabet
            res.append(i - m + 1)
    return res

Tests

Your solution should pass these:

assert anagram_starts("cbaebabacd", "abc") == [0, 6]
assert anagram_starts("abab", "ab") == [0, 1, 2]
assert anagram_starts("a", "ab") == []
assert anagram_starts("aaaa", "aa") == [0, 1, 2]
assert anagram_starts("xyz", "abc") == []

Explanation

Fixed window: add s[i], remove s[i - m] once the window is full, compare counts. With 26 letters the comparison is constant time, so the whole thing is O(26·n) = O(n).

Faster constant: keep matches = number of letters whose counts are equal. When a count changes, adjust matches for that letter only (+1 if it just became equal, -1 if it just stopped being equal). The window is an anagram when matches == 26.

Pitfall: sorting each window (sorted(s[i:i+m]) == sorted(p)) is O(n·m log m): fine as a first answer, but say you’d optimise it.

Follow-up questions

Unicode input with an unbounded alphabet?

Use dict counters and the matches technique keyed by character. Each step touches two keys, so it stays O(n).

Return only whether any anagram exists (permutation in string).

Same loop, return True on the first match. (LeetCode 567.)