DSA sheet · Strings · Sliding Window pattern (fixed size + frequency map)

Find All Anagrams in a String

This is the first sliding-window question of the strings section, and the teacher spends two videos on it. In Part 1 she explains what an anagram is, reads the constraints, and writes a brute force: cut out every substring of length len(p), count its letters, and compare the counts with p's counts. She also compares "sort and compare" against "count and compare", and does a careful time-complexity analysis with different sizes. In Part 2 she says why this is a sliding-window problem and solves it in O(n) two ways: first with two frequency maps (easier to understand), then with one map (less space, the one interviewers like).

Why it matters: "find every window of s that is a rearrangement of p" is the parent of a whole family: Permutation in String (problem 13) is the same code returning True/False, and Minimum Window Substring (problem 14) uses the same "count of still-needed characters" idea with a variable window.

Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the logic from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember

Part 0 · Before starting

Substring / subarray = a contiguous piece

A substring (for strings) or subarray (for arrays) is a piece whose characters sit next to each other. You pick a start index and an end index and take everything between them. You may not skip a character in the middle.

index01234
scbaeb"bae" (index 1..3) is a substring
scbaeb"cab" is not a substring (it skips letters)

The brute force over all windows, and why it repeats work

Many questions say "find the best / count all substrings (or subarrays) that satisfy some rule". The simplest answer is two loops: the outer loop picks the start, the inner loop walks the end forward and re-checks the piece. That is about n² pieces, so O(n²) (or worse, if each check is itself a loop).

The waste: two neighbouring pieces share almost everything. "cba" and "bae" share "ba". The brute force throws away what it learned about "ba" and counts it again from zero. Sliding window keeps the shared middle and only updates the two ends.

The window [left..right]

A window is the current substring, described by two indexes: left (its first character) and right (its last character). Its length is right − left + 1. Two moves change it:

The teacher's short version: right is responsible for expanding, left is responsible for shrinking, and left always undoes what right did. Both pointers only move forward, never back. Each one walks the string at most once, so the whole thing is about 2n steps = O(n).

Fixed-size vs variable-size windows

Fixed size (size k)Variable size
whenthe question tells you the length (here: exactly len(p))the question asks for the longest / shortest / number of windows that obey a rule
movesexpand by 1; if the length becomes k+1, shrink by exactly 1expand by 1; then while the window is invalid (or, for "shortest", while it is still valid) shrink
shrink usesif (one step is always enough)while (may need many steps)
this pageyes, every anagram has length len(p)problems 3–8, 11, 12, 14
template: fixed-size window of size k
def fixed_window(arr, k):
    out = []
    left = 0
    for right in range(len(arr)):
        # 1. add arr[right] to the window's data
        if right - left + 1 > k:       # window became k+1 long
            # 2. remove arr[left] from the window's data
            left += 1                  # exactly one step, so `if` not `while`
        if right - left + 1 == k:
            out.append((left, right))  # 3. use the full window
    return out

Longest valid, shortest valid, count of valid (for the variable kind)

Why sliding window needs a "monotonic" rule

Shrinking is only safe if removing a character can only move the window towards valid (and adding can only move it away, or the other way round, but always the same way). With positive numbers and a sum rule this holds: add → bigger, remove → smaller. With negative numbers it breaks (removing a negative makes the sum bigger), and sliding window gives wrong answers. For this problem the window length is fixed, so we never have to decide "should I shrink?" based on the data at all, which is why it is the easiest kind.

(One more tool you'll meet in problem 9, Sliding Window Maximum: a monotonic deque that keeps window elements in decreasing order so the max is always at the front. Not needed here.)

Frequency map (Counter)

A frequency map stores character → how many times it appears. In Python a dict works, and collections.Counter builds one in a single line.

frequency map three ways
from collections import Counter

freq = {}
for ch in "abca":
    freq[ch] = freq.get(ch, 0) + 1     # {'a': 2, 'b': 1, 'c': 1}

same = Counter("abca")                 # Counter({'a': 2, 'b': 1, 'c': 1})

arr = [0] * 26                         # only lowercase letters
for ch in "abca":
    arr[ord(ch) - ord('a')] += 1       # index 0 = 'a', 1 = 'b', ... 25 = 'z'

freq.get(ch, 0) means "the count of ch, or 0 if ch isn't in the map yet" (Java's getOrDefault). The 26-slot list works because the question says only lowercase English letters appear: ord(ch) − ord('a') turns 'a' into 0, 'b' into 1, … 'z' into 25.

What is an anagram?

An anagram of a word is any rearrangement (permutation) of its letters, using every letter exactly as many times as in the original. For "abc" the anagrams are: abc, acb, bac, bca, cab, cba (3! = 6 of them). Two strings are anagrams of each other exactly when they have the same length and the same frequency map. The order of letters doesn't matter, only the counts.


Part A · Brute force: count every substring of length k

LeetCode 438 · from video 1

1The question in simple words

You get two strings, s (long) and p (short). Find every place in s where a substring is an anagram of p, and return the list of starting indexes of those substrings.

index0123456789
scbaebabacdp = "abc" → answer [0, 6]

"cba" starts at index 0 and is a rearrangement of "abc". "bac" starts at index 6 and is too. No other length-3 piece uses exactly one a, one b and one c. So the answer is [0, 6]. The question wants the start index of each anagram, not the substrings themselves.

2What the constraints tell us

3Intuition: cut out every piece of length k and check it

Let k = len(p). An anagram of p must have length k, so we only care about substrings of length exactly k. Line them up: "cba", "bae", "aeb", "eba", "bab", "aba", "bac", "acd". For each one, ask "is this a rearrangement of p?" If yes, remember where it started.

4Building the logic from examples

How do we check "is this piece an anagram of p"? Two ways

  1. Sort both and compare: sorted("cba") = "abc" = sorted("abc") ✓. No extra array needed, but every check costs a sort.
  2. Count both and compare: make a 26-slot count array for p (p_count) and one for the piece (s_count). For p = "abc": a → 1, b → 1, c → 1, all other slots 0. For the piece "cba": c → 1, b → 1, a → 1. The two arrays are equal → anagram ✓.
Doubt 1: which one should I pick?
→ The teacher picks counting. Sorting takes no extra space, but each sort of a k-long piece costs k·log k, and we sort about n pieces, so it adds an extra log k factor of time. Counting a piece costs only k steps (plus a 26-step compare). We still have to fill and compare the array, so counting isn't free, but it removes that log factor. Time matters more here than 26 slots of memory.

Where can a piece start?

Call the start index i. The piece is s[i], s[i+1], …, s[i+k−1]. The last legal start is the one where the piece ends exactly at the last index n−1, so i + k − 1 = n − 1, which means i = n − k. So i runs from 0 to n − k, both included, which is n − k + 1 pieces. For s = "cbaebabacd" (n = 10), k = 3: i goes 0…7, 8 pieces. If you start at index 8, only two letters ("cd") are left, so it can't hold a 3-letter anagram.

Doubt 2: the inner loop: is the end i + k or i + k − 1?
→ Both are the same idea. The piece's last index is i + k − 1 (included). In a loop that stops before its end value, you write the end as i + k. In Python: for j in range(i, i + k) visits i … i+k−1.
Doubt 3: what do I store when a piece matches?
→ The start, which is i. That's exactly what the question asks for. So the outer pointer i doubles as "the answer if this piece works".
Doubt 4 (a fix): the outer loop must include n − k.
→ In the video she says i goes "till n − k". Make sure the last start n − k is actually tried: in Python that's range(n − k + 1). With range(n − k) you would miss an anagram at the very end, e.g. s = "xab", p = "ab" (answer [1]). The tests check this.

5Approach steps

  1. Make result = [], and let n = len(s), k = len(p).
  2. Build p_count: 26 zeros, then +1 for every letter of p.
  3. For every start i from 0 to n − k: make a fresh s_count of 26 zeros and count s[i … i+k−1] into it.
  4. If s_count == p_count, append i to result.
  5. Return result.

6Code (Python)

Brute force: count array per window
class Solution:
    def findAnagrams(self, s, p):
        result = []
        n, k = len(s), len(p)

        p_count = [0] * 26
        for ch in p:
            p_count[ord(ch) - ord('a')] += 1

        for i in range(n - k + 1):              # every legal start (empty if k > n)
            s_count = [0] * 26                  # fresh counts for this piece
            for j in range(i, i + k):           # s[i .. i+k-1]
                s_count[ord(s[j]) - ord('a')] += 1
            if s_count == p_count:              # same letters, same counts
                result.append(i)                # store the START index
        return result
Brute force: sort every window (the other option she mentions)
class Solution:
    def findAnagrams(self, s, p):
        k = len(p)
        target = sorted(p)
        return [i for i in range(len(s) - k + 1)
                if sorted(s[i:i + k]) == target]

7Code line by line

linewhat it means
result = []The list of start indexes we will return.
n, k = len(s), len(p)k is the length every anagram must have.
p_count = [0] * 26 for ch in p: ... += 1p's frequency array. Slot 0 is 'a', slot 25 is 'z'. Built once.
for i in range(n - k + 1):Try every start from 0 to n−k. If k > n this range is empty, so we return [] automatically.
s_count = [0] * 26A new count array for each piece. If you reused the old one, letters from the previous piece would leak in.
for j in range(i, i + k):Walk the k letters of this piece.
if s_count == p_count:Python compares the two lists slot by slot (26 checks).
result.append(i)i is the start of the anagram.

8Dry run (hand table)

s = "cbaebabacd", p = "abc", so p_count has a:1, b:1, c:1. Only non-zero slots are shown.

ipiece s[i..i+2]s_count (non-zero)equal to p_count?result
0cbaa:1 b:1 c:1yes[0]
1baea:1 b:1 e:1no (e, no c)[0]
2aeba:1 b:1 e:1no[0]
3ebaa:1 b:1 e:1no[0]
4baba:1 b:2no (two b)[0]
5abaa:2 b:1no (two a)[0]
6baca:1 b:1 c:1yes[0, 6]
7acda:1 c:1 d:1no[0, 6]

Final answer: [0, 6] ✓. Notice how rows 1, 2, 3 count "a", "b", "e" over and over. That repetition is what Part B removes.

9Complexity & remember

The teacher works out the time by trying different sizes, to show that "plug in the biggest k" is not automatically the worst case.

She submitted the counting version: accepted, but slow. So she looks for a better pattern.

Remember the brute forceEvery anagram has length k = len(p). Try every start 0…n−k, count the k letters into a fresh 26-array, compare with p's array, store the start. Counting beats sorting by a log factor.

Part B · Sliding window with two maps

LeetCode 438 · from video 2

1The question again, with the new goal

Same question (all start indexes of anagrams of p in s), same example: s = "cbaebabacd", p = "abc" → [0, 6]. The goal now is one pass over s, O(n), with no inner loop.

2What the constraints tell us now

3Intuition: why sliding window, and the "count" idea

Why sliding window? The teacher gives two reasons:

  1. Strings have two main patterns. Two pointers is for comparing or computing with exactly two positions (a left character and a right character). Sliding window is for questions about a substring/subarray (everything between left and right). We want substrings → sliding window.
  2. The brute force repeats work: when we moved from "cba" to "bae" we re-counted "ba". Sliding window keeps the middle and only changes the ends, turning two loops into one.

The picture: keep a window of length k sliding over s. Keep two maps:

Comparing two maps every step would be slow-ish, so she adds one number: count = how many required characters the window is still missing. It starts at len(p) (we're missing all 3). Every time a useful character enters, count goes down by 1. When count == 0 and the window has length k, the window holds exactly the letters of p → anagram.

4Building the logic from examples

Example 1: "cba…" – expanding and reducing count

count = 3. right reads c: is c in p_map with a count > 0? Yes → it's needed. Put it in s_map (c:1) and count → 2. right reads b: needed → s_map b:1, count → 1. right reads a: needed → a:1, count → 0. Count 0 means every required character has been seen in the window, and the window length is 3 = k → valid. Store the start, which is left = 0 (not right!).

Note that every character right reads goes into s_map, needed or not. Only count is touched just for needed ones.

Example 2: "eabc" – why we sometimes have to shrink

Imagine s = "eabc", p = "abc". right reads e (not needed, count stays 3), then a (2), b (1), c (0). count is 0, but the window is "eabc", length 4. The answer is the start of "abc" (index 1), not the start of "eabc". So count 0 is not enough on its own; the length must also be exactly k. When the window is longer than k, we shrink from the left. In "cba" the length was already 3, so no shrinking was needed there.

Shrinking: left undoes what right did

Back to "cbaebabacd". After storing 0, right reads e → window "cbae", length 4 > 3 → shrink. The letter leaving is c. Before it leaves, the window had everything; after it leaves, "bae" is missing a c. So count must go back up to 1 ("I need one more character").

In code: if the left character is in p_map and it was a required copy (s_map[ch] ≤ p_map[ch]), then count += 1. In both cases, s_map[ch] −= 1 (undo right's +1), and left += 1.

The tricky part: duplicates. "Present in p" is not enough

Now window is "bae" (count 1, missing c). right reads b → s_map b becomes 2. If our rule were only "b is in p_map", we would lower count to 0 and wrongly call "aeb"(+b) valid. But p needs only one b, and the window already had one. The second b is extra.

So the rule when a character enters is: after adding it, if s_map[ch] ≤ p_map[ch], it was needed → count −= 1; if s_map[ch] > p_map[ch], it's extra → leave count alone. The teacher acts it out: s_map says "I've seen two b's", p_map says "I only need one. If you're equal to me or below, I'll take it. You're above me, so this b is not useful."

The same rule protects shrinking: if the leaving b has s_map[b] = 2 while p_map[b] = 1, there is still another b inside, so losing this one doesn't hurt → count unchanged. Only when s_map[ch] ≤ p_map[ch] (we're about to drop below what's needed) does count go up.

Doubt 1: why is the leaving check "≤" and done before the −1?
→ Before removing, s_map[ch] is the count including this copy. If it's ≤ p_map[ch], every copy in the window is needed, so this one is needed too: losing it makes us miss one → count += 1. If it's > p_map[ch], we have a spare. Then we subtract. Checking after the subtraction would need "<" instead. Same idea, different moment.
Doubt 2: why shrink with if, not while?
→ The window only ever grows by one character per step. If it was length k, it becomes k+1, and one shrink brings it back to k. It can never be k+2. So one step is always enough. This is the fixed-size window from Part 0.
Doubt 3: after shrinking, is "count == 0" enough to store left? What about the length?
→ After the shrink, the length is at most k. count == 0 means k needed characters are inside, so the length is at least k. Together: exactly k. That's why her code checks only count. Early on (right < k−1) the window is short, but then count can't be 0 yet.
Doubt 4: in the video's walk-through of the second anagram, she lets the window grow to "baebabac" and then shrinks many times. Does the code do that?
→ No. Her spoken walk-through around index 4–8 lets the window grow (b goes up to 3, a to 3) and then drops several letters on the left in a row. The code she writes shrinks once per step, so the window stays at length 3 and s_map[b] never goes above 2. Both reach the same answer (6). The dry run below follows the code. The duplicate rule (s_map ≤ p_map) is exactly the same in both.

5Approach steps

  1. If len(s) < len(p): return [].
  2. Build p_map from p. s_map = {}, left = 0, count = len(p).
  3. For right from 0 to n−1: ch = s[right]; s_map[ch] += 1.
  4. If ch is in p_map and s_map[ch] ≤ p_map[ch] → count −= 1 (a needed character came in).
  5. If the window length right−left+1 > k: let lc = s[left]. If lc is in p_map and s_map[lc] ≤ p_map[lc] → count += 1. Then s_map[lc] −= 1, left += 1.
  6. If count == 0 → append left.
  7. Return the result.

6Code (Python)

Sliding window with two maps
from collections import Counter

class Solution:
    def findAnagrams(self, s, p):
        result = []
        if len(s) < len(p):                     # base case: no room for an anagram
            return result

        p_map = Counter(p)                      # what we NEED
        s_map = {}                              # what the window HAS
        left = 0
        count = len(p)                          # needed characters still missing
        k = len(p)

        for right in range(len(s)):
            ch = s[right]
            s_map[ch] = s_map.get(ch, 0) + 1    # every char enters the map
            if ch in p_map and s_map[ch] <= p_map[ch]:
                count -= 1                      # a needed copy arrived

            if right - left + 1 > k:            # window too long: shrink once
                lc = s[left]
                if lc in p_map and s_map[lc] <= p_map[lc]:
                    count += 1                  # losing a needed copy
                s_map[lc] -= 1                  # undo what right did
                left += 1

            if count == 0:                      # length is k and nothing missing
                result.append(left)
        return result

7Code line by line

linewhat it means
if len(s) < len(p): return resultHer base case. A shorter s can't contain a k-letter anagram.
p_map = Counter(p)Letter → how many we need. For "abc": a:1, b:1, c:1.
s_map = {}Letter → how many are inside the current window.
count = len(p)At the start we're missing all k needed characters.
for right in range(len(s)):right walks once from 0 to n−1 (expanding).
s_map[ch] = s_map.get(ch, 0) + 1The new letter is now in the window, whether it's useful or not.
if ch in p_map and s_map[ch] <= p_map[ch]: count -= 1Only a needed copy reduces count. A letter not in p, or an extra copy of a letter in p, leaves count alone.
if right - left + 1 > k:The window is k+1 long, so we must drop the leftmost letter.
if lc in p_map and s_map[lc] <= p_map[lc]: count += 1Checked before the −1: if this copy was needed, after it leaves we'll be missing one.
s_map[lc] -= 1 left += 1Undo right's +1 and move the window's start forward.
if count == 0: result.append(left)Window length is k and nothing is missing → anagram starting at left.

8Dry run (hand table, s_map after every slide)

s = "cbaebabacd", p = "abc", k = 3, p_map = {a:1, b:1, c:1}. Start: s_map = {}, left = 0, count = 3. "needed" means s_map[ch] ≤ p_map[ch] after the +1.

stepright (char added)window [l..r] before shrinks_map after addingcount after addingshrink? (what leaves, why)window after shrinks_map after shrinkcountanswer so far
10 (c) needed[0..0] cc:12no (len 1)cc:12[]
21 (b) needed[0..1] cbc:1 b:11no (len 2)cbc:1 b:11[]
32 (a) needed[0..2] cbac:1 b:1 a:10no (len 3)[0..2] cbac:1 b:1 a:10[0]
43 (e) extra (not in p)[0..3] cbaec:1 b:1 a:1 e:10yes, len 4: c leaves; s_map[c]=1 ≤ 1 → needed → count+1[1..3] baec:0 b:1 a:1 e:11[0]
54 (b) extra (b:2 > 1)[1..4] baebc:0 b:2 a:1 e:11yes: b leaves; s_map[b]=2 > 1 → spare, count same[2..4] aebc:0 b:1 a:1 e:11[0]
65 (a) extra (a:2 > 1)[2..5] aebac:0 b:1 a:2 e:11yes: a leaves; a=2 > 1 → spare[3..5] ebac:0 b:1 a:1 e:11[0]
76 (b) extra (b:2)[3..6] ebabc:0 b:2 a:1 e:11yes: e leaves; not in p[4..6] babc:0 b:2 a:1 e:01[0]
87 (a) extra (a:2)[4..7] babac:0 b:2 a:2 e:01yes: b leaves; b=2 > 1 → spare[5..7] abac:0 b:1 a:2 e:01[0]
98 (c) needed (c:1 ≤ 1)[5..8] abacc:1 b:1 a:2 e:00yes: a leaves; a=2 > 1 → spare, count stays 0[6..8] bacc:1 b:1 a:1 e:00[0, 6]
109 (d) extra[6..9] bacdc:1 b:1 a:1 e:0 d:10yes: b leaves; b=1 ≤ 1 → needed → count+1[7..9] acdc:1 b:0 a:1 e:0 d:11[0, 6]

right goes past the end → loop ends → return [0, 6] ✓.

The string at three key moments (yellow = window, grey = already left behind):

step 3cbaebabacdcount 0, length 3 → store left = 0
LR
step 4cbaebabacde came in, c left → missing c, count 1
LR
step 9cbaebabacdc came in (needed), spare a left → count 0 → store 6
LR

9Complexity & remember

Remember two mapscount starts at len(p). Enter: s_map +1, and if s_map ≤ p_map → count −1. Too long: if s_map[left] ≤ p_map[left] → count +1, then s_map −1, left +1. count == 0 → store left.

Part C · Sliding window with one map

LeetCode 438 · from video 2 · same window, same count, half the maps

1What stays the same

Same question, same constraints, same fixed-size window, same count of missing characters, same "shrink once with if", same "store left when count == 0". Time is the same O(n). Only the bookkeeping changes: we drop s_map and let one map, need, mean "how many more of this letter the window still needs". The teacher's view: one-map is not harder, it just makes more sense after you've seen two maps. Interviewers usually prefer this one because it uses less space.

2Intuition: one map that counts down

3Building the conditions

When right adds a letter: did it reduce count?

It's needed if need[ch] was positive before we decrease it. Two equal ways to write it:

Doubt 1: why "> 0" in one version and ">= 0" in the other?
→ The teacher stresses this. It's the same test at two different moments. Before the −1, "needed" means at least 1 left (> 0). After the −1, that same letter shows ≥ 0. Mix them up and you get wrong counts. Pick one order and match the sign to it.
Doubt 2: why does e start at 0 and go to −1?
→ So that a letter p doesn't have looks exactly like an extra copy: negative. Then one rule covers both. When e later leaves, it goes back up to 0, still not ≥ 1, and count isn't touched. In Python, need.get(ch, 0) gives that starting 0.

When left removes a letter: did it increase count?

Look at need[lc] before increasing it. If it's ≥ 0, this copy was a needed one: when right brought it in, need was positive and went down to this value. Losing it means we're missing one → count += 1. If it's negative, the window has a spare copy, so count stays. Either way, need[lc] += 1, then left += 1.

Her example: window "cbae" with need = {a:0, b:0, c:0, e:−1}. c leaves: need[c] = 0, which is ≥ 0 → it was needed → count 0 → 1, and need[c] becomes 1 ("we need one c again").

Doubt 3: why is 0 counted as "needed" when leaving?
→ 0 means the window has exactly as many of this letter as p wants, no spare. Remove one and we'd be short. Only a negative value says "there's a spare, you can lose one".

4Approach steps

  1. If len(s) < len(p): return [].
  2. need = counts of p; left = 0; count = len(p).
  3. For each right: ch = s[right]. If need.get(ch, 0) > 0 → count −= 1. Then need[ch] = need.get(ch, 0) − 1.
  4. If the window is longer than k: lc = s[left]; if need[lc] ≥ 0 → count += 1; need[lc] += 1; left += 1.
  5. If count == 0 → append left.
  6. Return result.

5Code (Python)

Sliding window with one map
class Solution:
    def findAnagrams(self, s, p):
        result = []
        if len(s) < len(p):
            return result

        need = {}                               # letter -> how many still needed
        for ch in p:
            need[ch] = need.get(ch, 0) + 1
        left = 0
        count = len(p)
        k = len(p)

        for right in range(len(s)):
            ch = s[right]
            if need.get(ch, 0) > 0:             # check first ...
                count -= 1                      # ... it was needed
            need[ch] = need.get(ch, 0) - 1      # then decrease (may go negative)

            if right - left + 1 > k:
                lc = s[left]
                if need[lc] >= 0:               # it was a needed copy
                    count += 1
                need[lc] += 1                   # undo what right did
                left += 1

            if count == 0:
                result.append(left)
        return result
One map, decrease-first variant (the ">= 0" version she also explains)
class Solution:
    def findAnagrams(self, s, p):
        result = []
        if len(s) < len(p):
            return result
        need = {}
        for ch in p:
            need[ch] = need.get(ch, 0) + 1
        left, count, k = 0, len(p), len(p)
        for right in range(len(s)):
            ch = s[right]
            need[ch] = need.get(ch, 0) - 1      # decrease first ...
            if need[ch] >= 0:                   # ... so the test is >= 0
                count -= 1
            if right - left + 1 > k:
                lc = s[left]
                if need[lc] >= 0:
                    count += 1
                need[lc] += 1
                left += 1
            if count == 0:
                result.append(left)
        return result

6Code line by line (only the lines that differ from Part B)

linewhat it means
need = {} ... need[ch] + 1Only one map. At the start it equals p's counts.
if need.get(ch, 0) > 0: count -= 1Still needed some of this letter? Then this copy is useful. A letter not in p reads as 0 → not useful.
need[ch] = need.get(ch, 0) - 1Every letter that enters lowers its need, even if it goes negative (= extra).
if need[lc] >= 0: count += 1The leaving copy was not a spare, so after it leaves we're missing one.
need[lc] += 1Left undoes right's −1.

7Dry run (hand table, the one map after every slide)

s = "cbaebabacd", p = "abc". Start: need = {a:1, b:1, c:1}, left = 0, count = 3. "need before" is the value right sees before it decreases.

stepright (char, need before)window before shrinkneed after addingcount after addingshrink? (what leaves, why)window after shrinkneed after shrinkcountanswer so far
10 c (1 > 0 → needed)ca:1 b:1 c:02noca:1 b:1 c:02[]
21 b (1 → needed)cba:1 b:0 c:01nocba:1 b:0 c:01[]
32 a (1 → needed)cbaa:0 b:0 c:00no (len 3)[0..2] cbaa:0 b:0 c:00[0]
43 e (0 → extra)cbaea:0 b:0 c:0 e:−10yes: c leaves, need[c]=0 ≥ 0 → count+1[1..3] baea:0 b:0 c:1 e:−11[0]
54 b (0 → extra)baeba:0 b:−1 c:1 e:−11yes: b leaves, need[b]=−1 → spare[2..4] aeba:0 b:0 c:1 e:−11[0]
65 a (0 → extra)aebaa:−1 b:0 c:1 e:−11yes: a leaves, need[a]=−1 → spare[3..5] ebaa:0 b:0 c:1 e:−11[0]
76 b (0 → extra)ebaba:0 b:−1 c:1 e:−11yes: e leaves, need[e]=−1 → spare[4..6] baba:0 b:−1 c:1 e:01[0]
87 a (0 → extra)babaa:−1 b:−1 c:1 e:01yes: b leaves, need[b]=−1 → spare[5..7] abaa:−1 b:0 c:1 e:01[0]
98 c (1 → needed)abaca:−1 b:0 c:0 e:00yes: a leaves, need[a]=−1 → spare[6..8] baca:0 b:0 c:0 e:00[0, 6]
109 d (0 → extra)bacda:0 b:0 c:0 e:0 d:−10yes: b leaves, need[b]=0 ≥ 0 → count+1[7..9] acda:0 b:1 c:0 e:0 d:−11[0, 6]

return [0, 6] ✓. Notice: at every valid window (steps 3 and 9) all of p's letters show exactly 0, and nothing is negative.

step 5cbaebabacdneed = a:0 b:0 c:1 e:−1 → c still missing
LR
step 10cbaebabacdthe needed b left → count 1, no new answer
LR

8The "eabc" example with one map

s = "eabc", p = "abc": e → need e:−1 (count 3); a → a:0 (2); b → b:0 (1); c → c:0 (0), but length 4 → shrink: e leaves with need −1 (spare) → count stays 0, e back to 0, left = 1 → count 0 → store 1. Answer [1] ✓, the start of "abc", not of "eabc".

9Complexity & remember

Remember one mapneed = counts of p. Enter: if need > 0 → count −1; need −1. Leave: if need ≥ 0 → count +1; need +1. Negative = extra. count == 0 → store left.

Part D · Revision page

Brute forceTwo mapsOne map
ideacount every k-piece from scratchwindow keeps s_map; compare with p_map through countone map "need" counting down; negative = extra
needed on enter—s_map[ch] ≤ p_map[ch] (after +1)need[ch] > 0 (before −1) or ≥ 0 (after −1)
needed on leave—s_map[lc] ≤ p_map[lc] (before −1)need[lc] ≥ 0 (before +1)
valid whens_count == p_countcount == 0count == 0
storeileftleft
timeO((n−k+1)·k), O(n²) worstO(n)O(n)
spaceO(1) (26 slots)two maps (≤ 26 keys each)one map
If you remember only 5 lines 1. Anagram = same length + same letter counts, so every answer window has length k = len(p).
2. Fixed window: expand right by 1; if length > k, shrink left by 1 (if, not while).
3. count = needed letters still missing; starts at k; anagram when it hits 0.
4. Only a needed copy changes count. Extra copies (or letters not in p) never do.
5. Left undoes right. Store left, never right.
Mistakes to avoid ✗ lowering count for every letter that is "in p" (duplicates break it: use ≤ / > 0)
✗ mixing "> 0" with "decrease first" (must be "≥ 0" then)
✗ checking the leaving letter after changing its count
✗ storing right (or right − k) instead of left
✗ outer brute-force loop that stops at n − k − 1 (misses the last window)
✗ reusing the brute-force count array between pieces
✗ forgetting len(s) < len(p) → []
test it yourself (paste under any Solution above)
sol = Solution()
print(sol.findAnagrams("cbaebabacd", "abc"))   # [0, 6]
print(sol.findAnagrams("abab", "ab"))          # [0, 1, 2]
print(sol.findAnagrams("eabc", "abc"))         # [1]
print(sol.findAnagrams("xab", "ab"))           # [1]
print(sol.findAnagrams("a", "ab"))             # []
print(sol.findAnagrams("aaaa", "aa"))          # [0, 1, 2]

Based on these videos: Find All Anagrams in a String · Part 1 (brute force) · Find All Anagrams in a String · Part 2 (sliding window: two maps & one map)