Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Two Pointers & Sliding Window

Builds on Hash Map, which you haven’t finished yet.

P0EasyMental model~4 min speedrun

Fixed-Size Sliding Window

Grows into
Monotonic Deque
Unlocks
Longest Window
The problem

Find All Anagrams in a String (LC 438)(opens leetcode.com)

You have two strings, s and p. Find every starting index in s where the substring of length len(p) beginning there is an anagram of p — i.e. uses exactly the same letters, rearranged. Return those indices, in any order.

Example 1
Input:s = "cbaebabacd", p = "abc"
Output:[0, 6]
Starting at index 0, s gives "cba" — a rearrangement of "abc". Starting at index 6, s gives "bac" — also a rearrangement of "abc". Both indices qualify.

Fixed Window

i >= k -> (Absorb Incoming, Evict Outgoing)
Spot it when: The window length k is given and you measure every window: averages, counts, anagram matches.
Fixed Window
Python
1from typing import Callable, List
2 
3 
4def slide_fixed(n: int, k: int, add: Callable[[int], None], remove: Callable[[int], None], record: Callable[[int], None]) -> None:
5 """Fixed Window: visit every window of exactly k indices in 0..n-1, one index in and one out per step.
6 Precondition: n, the number of indices, is known before the loop starts, and k >= 1.
7 record(start) is called once per window, after the window holds indices start..start + k - 1.
8 T = the cost of one add, remove or record call.
9 Time: O(N * T) | Space: O(1) besides the window's state
10 """
11 # 1. Handle Impossible Case:
12 # Fewer than k indices exist, so no window of length k can ever form.
13 if n < k:
14 return
15 
16 # 2. Build First Window:
17 # Add every index from 0 to k - 1 so the window holds its first k indices before any sliding starts.
18 for i in range(k):
19 add(i)
20 record(0)
21 
22 # 3. Slide One Step At A Time:
23 # Add the incoming index i, remove the outgoing index i - k, then record only after both updates land so the window always holds exactly k indices when it is read.
24 for i in range(k, n):
25 add(i)
26 remove(i - k)
27 record(i - k + 1)
Invariant: After each slide, window_count holds the letter counts of the k characters ending at index i, s[i - k + 1..i], and result lists every earlier window whose counts equal p_count.

Every window has the length k = len(p), so build the first window's counts once, then slide one step at a time: add the letter entering on the right and remove the one leaving on the left. Each slide changes exactly two letters of the window, so updating window_count for s[i] and s[i - k] keeps it equal to the counts of the k characters ending at i without recounting the whole window, and window_count == p_count then says whether that window is an anagram of p. The trap is the index of the leaving letter: when s[i] enters, s[i - k] leaves; an off-by-one there shifts every window, so the windows compared are the wrong ones.

See it run

Fixed Window, step by step

i - · window_count empty · p_count a1 b1 c1 · result []

i 2 · window_count a1 b1 c1 · p_count a1 b1 c1 · result []

i 2 · window_count a1 b1 c1 · p_count a1 b1 c1 · result [0]

i 3 · window_count a1 b1 c1 · p_count a1 b1 c1 · result [0]

i 3 · window_count a1 b1 e1 · p_count a1 b1 c1 · result [0]

i 4 · window_count a1 b1 e1 · p_count a1 b1 c1 · result [0]

i 5 · window_count a1 b1 e1 · p_count a1 b1 c1 · result [0]

i 6 · window_count a1 b2 · p_count a1 b1 c1 · result [0]

i 7 · window_count a2 b1 · p_count a1 b1 c1 · result [0]

i 8 · window_count a1 b1 c1 · p_count a1 b1 c1 · result [0,6]

i 9 · window_count a1 c1 d1 · p_count a1 b1 c1 · result [0,6]

i - · window_count a1 c1 d1 · p_count a1 b1 c1 · result [0,6] · answer [0,6]

Each box is one letter of s, numbered by its index. The window always covers k letters.

  • enter the letter s[i] that the loop adds this step
  • leave the letter s[i - k] that the loop removes this step
  • stay a letter that is inside the window and is not compared as a match
  • match a letter of a window whose window_count equals p_count
  • not out the letter s[i - k + 1] that a wrong removal would take out

Start: s = "cbaebabacd" and p = "abc", so k = 3. Every window has 3 letters, and p_count holds the letters of p.

Build the first window s[0..2] = "cba" by adding its 3 letters. window_count is now a1 b1 c1.

window_count equals p_count, so the window starting at 0 is an anagram and 0 joins result.

Trap: when s[3] enters, the letter that leaves is s[i - k] = s[0], not s[i - k + 1] = s[1] = "b". That letter is still inside the window.

Add s[3] = "e" and remove s[0] = "c". window_count does not equal p_count.

Add s[4] = "b" and remove s[1] = "b". window_count does not equal p_count.

Add s[5] = "a" and remove s[2] = "a". window_count does not equal p_count.

Add s[6] = "b" and remove s[3] = "e". window_count does not equal p_count.

Add s[7] = "a" and remove s[4] = "b". window_count does not equal p_count.

Add s[8] = "c" and remove s[5] = "a". window_count equals p_count, so 6 joins result.

Add s[9] = "d" and remove s[6] = "b". window_count does not equal p_count.

Done: result = [0, 6], the start of every window that is an anagram of p.

0c
1b
2a
3e
4b
5a
6b
7a
8c
9d
1 / 12
Gotchas1 of 5

Gotcha 1 of 5

  1. 1
    The trap

    Gotcha 1: Removing s[i - k + 1] instead of s[i - k]

    Mistake
    window_count[ord(s[i - k + 1]) - ord('a')] -= 1
    Fix
    window_count[ord(s[i - k]) - ord('a')] -= 1

    What happens: Before the step, the window is s[i - k..i - 1]. When s[i] enters, the letter that leaves is s[i - k]. Removing s[i - k + 1] takes out a letter that is still in the window. The counts then match no real window. The result lists wrong start indices.

    Example: s = "cbaebabacd", p = "abc"
    Mistake: returns [0, 3, 4, 5]Fix: returns [0, 6]
  2. 2

    Gotcha 2: Writing the outgoing index as s[k - i] instead of s[i - k]

    Mistake
    window_count[ord(s[k - i]) - ord('a')] -= 1
    Fix
    window_count[ord(s[i - k]) - ord('a')] -= 1

    What happens: The loop runs i from k upward, and the letter that leaves at step i is s[i - k]. The index i - k grows by 1 at each step, because the window moves right. The index k - i does the opposite: it is 0 at i = k, then -1, -2 and so on. Python reads a negative index from the end of the string and raises no error. With s = "cbaebabacd" and k = 3, the first slide at i = 3 removes s[0], which is correct. At i = 4 the code removes s[-1], the last letter 'd', instead of s[1], the letter 'b'. The count of 'd' becomes -1 and the count of 'b' stays too high. From then on, window_count matches no real window, so no later window can equal p_count.

    Example: s = "cbaebabacd", p = "abc"
    Mistake: returns [0]Fix: returns [0, 6]
  3. 3

    Gotcha 3: Comparing between the add and the remove

    Mistake
    window_count[ord(s[i]) - ord('a')] += 1
    if window_count == p_count:
        result.append(i - k + 1)
    window_count[ord(s[i - k]) - ord('a')] -= 1
    Fix
    window_count[ord(s[i]) - ord('a')] += 1
    window_count[ord(s[i - k]) - ord('a')] -= 1
    if window_count == p_count:
        result.append(i - k + 1)

    What happens: After the add, the window holds k + 1 letters. p_count holds exactly k letters. So the comparison is always False at that point. Only the first window, checked before the loop, can still match.

    Example: s = "cbaebabacd", p = "abc"
    Mistake: returns [0]Fix: returns [0, 6]
  4. 4

    Gotcha 4: Dropping the len(s) < k check

    What happens: When s is shorter than p, no window of length k fits. In Python, s[:k] then returns the whole shorter string without an error. Its counts never equal p_count, so the result is still []. In Java or C++, reading k characters from a shorter string goes past its end. So return [] before the first window is built.

  5. 5

    Gotcha 5: Re-summing every window in Maximum Average Subarray I (LC 643)

    Mistake
    window_sum = sum(nums[i - k + 1:i + 1])
    Fix
    window_sum += nums[i] - nums[i - k]

    What happens: Maximum Average Subarray I (LC 643) slides a sum instead of counts. sum(nums[i - k + 1:i + 1]) reads k values at every step. The whole scan then costs O(N · k). Adding the entering value and subtracting the leaving one costs O(1) per step. Both give the same answer.

Not this one if…
  • No window size is given: you want the longest window that stays within a limit, as in Max Consecutive Ones II (LC 487). See Longest Window

Deep Dive

Why one letter in and one letter out is enough

A fixed window has the same length k at every position. The window that ends at index i is s[i - k + 1..i]. The next window ends at i + 1, and it is the same letters with two changes. The letter s[i - k + 1] is gone from the left side, and s[i + 1] is new on the right side.

So the card never counts a window again. It keeps window_count and changes two entries per step: +1 for the letter that enters and -1 for the letter that leaves. After that, window_count is the exact letter count of the new window.

In Find All Anagrams in a String (LC 438), two strings are anagrams when every letter has the same count in both. So the test for one window is window_count == p_count. The two lists have 26 entries each, so the comparison costs a fixed amount of work.

Counting every window again costs k steps per window. For len(s) = 100000 and k = 50000, there are 50001 windows, and the work is about 2,500,050,000 steps. The slide costs two updates and one comparison per window, so the work is proportional to len(s).

The slide traced on s = "cbaebabacd" and p = "abc"

Here k = 3, p_count is one a, one b and one c, and s has 10 letters, so there are 8 windows.

The first window is s[0..2] = "cba". Its counts equal p_count, so result starts as [0].

Each slide from i = 3 to i = 9 adds s[i] and removes s[i - 3]. The windows from start 1 to start 5 are bae, aeb, eba, bab and aba. None of them has the counts of abc.

The window at start 6 is bac. It is added at i = 8, when c enters and a leaves. Its counts equal p_count, so 6 joins result. The window at start 7 is acd, and it does not match. The answer is [0, 6].

The order inside one slide

Inside one slide, the comparison comes after both updates. The window holds k letters only after the add and the remove are both done. Between them it holds k + 1 letters, and p_count holds k letters, so a comparison there always fails.

The leaving index is i - k. At the first slide, i = k, so the leaving letter is s[0], the first letter of the previous window. The index i - k + 1 is the first letter of the new window. It must stay.

Before the first slide, the loop for i in range(k) builds the first window and the code records it. That window has no letter to remove, so it is handled before the slide loop.

If len(s) < k, no window fits, and the card returns [] before building anything.

The same slide with a sum, and with a yes or no answer

The template does not depend on letter counts. It only needs a state that an add call and a remove call can update in constant time.

In Maximum Average Subarray I (LC 643), the state is one number: window_sum += nums[i] - nums[i - k]. For nums = [1, 12, -5, -6, 50, 3] and k = 4, the best window sum is 51, so the answer is 51 / 4 = 12.75.

In Permutation in String (LC 567), the state is the same window_count. The question is only whether any window equals p_count. For s1 = "ab" and s2 = "eidbaooo", the window ba matches, so the answer is True. For s2 = "eidboaoo", no window matches, so the answer is False.

When the alphabet is large, comparing two lists is slow. Keep a counter mismatched of the letters whose counts differ, and update it when a count changes. A window matches when mismatched == 0.

60-Second Pitch

"The window length k is fixed, so I do not count each window again. I keep window_count, the letter counts of the current window. When I move one step right, s[i] enters and s[i - k] leaves. That is two small updates, and window_count stays exact for the k letters that end at i. I compare window_count with p_count only after both updates, because the window has k letters only then. The comparison has 26 entries, so it costs a fixed amount. The time is O(N) and the extra space is O(1) for 26 letters. The same template slides a sum for Maximum Average Subarray I."

Common Questions

Why is the work O(N) when each step compares two lists of 26 entries?

The list has 26 entries however long s is, so one comparison costs a fixed amount of work. There are about N windows, and each costs two updates and one comparison. The total is proportional to N.

Which letter leaves when s[i] enters, and why?

The letter s[i - k] leaves. Before the step, the window is s[i - k..i - 1]. After s[i] enters, the window must be s[i - k + 1..i], so only s[i - k] is outside it.

What changes if the alphabet is large, for example all Unicode letters?

Use a dictionary for the counts. Comparing two dictionaries costs about the number of different letters, so keep a number mismatched of the letters whose counts differ. Update it when a count changes. A window matches when mismatched is 0, and each step costs a fixed amount again.

How does the same template solve Maximum Average Subarray I (LC 643)?

The state is one sum. Each step does window_sum += nums[i] - nums[i - k] and updates the best sum. For [1, 12, -5, -6, 50, 3] with k = 4 the best sum is 51, so the average is 12.75.

When is a fixed window the wrong tool?

It is wrong when the window length is not given and the window must grow or shrink to keep a condition. That is the case in the Longest Window and the Shortest Window cards. It is also wrong when you need the maximum of each window and a sum does not give it. That case needs the Monotonic Deque card.

SWE Reasoning

Spot it

  • The window length k is given in the problem, or equals the length of another input.
  • The question is asked for every window: the best sum, a match with a pattern, a count of windows.
  • The state of the window can change in constant time when one element enters and one leaves (a sum, a letter count, a count of vowels).
  • Example problems: Find All Anagrams in a String (LC 438), Maximum Average Subarray I (LC 643), Permutation in String (LC 567).

The invariant, step by step

  1. Before the slide loop, window_count holds the counts of s[0..k-1], and the card compares it to p_count.
  2. At index i, s[i] enters, so its count rises by one.
  3. At the same index, s[i - k] leaves, so its count falls by one.
  4. Now the window is s[i - k + 1..i], and it holds exactly k letters.
  5. window_count equals the counts of those k letters, so window_count == p_count is true exactly when the window is an anagram of p.
  6. If it is true, the start index i - k + 1 joins result.

Traps

  • Removing s[i - k + 1]. That letter is still in the window. Every later count is wrong, and for "cbaebabacd" with "abc" the result is [0, 3, 4, 5] instead of [0, 6].
  • Comparing before the remove. The window has k + 1 letters at that point, so the comparison fails. Only the first window can still match, and the result is [0].
  • Writing s[k - i] for the leaving letter. The index falls as i grows. In Python it becomes negative and reads from the end of the string without an error.
  • Forgetting the case len(s) < k. In some languages, building the first window then reads past the end of the string.
  • Summing the whole window at every step. The answer is correct but the work is O(N * k) instead of O(N).

Practice

Canonical LeetCode:Find All Anagrams in a String (LC 438)Maximum Average Subarray I (LC 643)Maximum Number of Vowels in a Substring of Given Length (LC 1456)Number of Sub-arrays of Size K and Average Greater than or Equal to Threshold (LC 1343)Moving Average from Data Stream (LC 346)Minimum Swaps to Group All 1's Together (LC 1151)Permutation in String (LC 567)Contains Duplicate II (LC 219)
Theory Context•Two Pointers & Sliding Window

Builds on Hash Map, which you haven’t finished yet.

P0EasyMental model~4 min speedrun

Fixed-Size Sliding Window

Grows into
Monotonic Deque
Unlocks
Longest Window
The problem

Find All Anagrams in a String (LC 438)(opens leetcode.com)

You have two strings, s and p. Find every starting index in s where the substring of length len(p) beginning there is an anagram of p — i.e. uses exactly the same letters, rearranged. Return those indices, in any order.

Example 1
Input:s = "cbaebabacd", p = "abc"
Output:[0, 6]
Starting at index 0, s gives "cba" — a rearrangement of "abc". Starting at index 6, s gives "bac" — also a rearrangement of "abc". Both indices qualify.

Fixed Window

i >= k -> (Absorb Incoming, Evict Outgoing)
Spot it when: The window length k is given and you measure every window: averages, counts, anagram matches.
Fixed Window
Python
1from typing import Callable, List
2 
3 
4def slide_fixed(n: int, k: int, add: Callable[[int], None], remove: Callable[[int], None], record: Callable[[int], None]) -> None:
5 """Fixed Window: visit every window of exactly k indices in 0..n-1, one index in and one out per step.
6 Precondition: n, the number of indices, is known before the loop starts, and k >= 1.
7 record(start) is called once per window, after the window holds indices start..start + k - 1.
8 T = the cost of one add, remove or record call.
9 Time: O(N * T) | Space: O(1) besides the window's state
10 """
11 # 1. Handle Impossible Case:
12 # Fewer than k indices exist, so no window of length k can ever form.
13 if n < k:
14 return
15 
16 # 2. Build First Window:
17 # Add every index from 0 to k - 1 so the window holds its first k indices before any sliding starts.
18 for i in range(k):
19 add(i)
20 record(0)
21 
22 # 3. Slide One Step At A Time:
23 # Add the incoming index i, remove the outgoing index i - k, then record only after both updates land so the window always holds exactly k indices when it is read.
24 for i in range(k, n):
25 add(i)
26 remove(i - k)
27 record(i - k + 1)
Invariant: After each slide, window_count holds the letter counts of the k characters ending at index i, s[i - k + 1..i], and result lists every earlier window whose counts equal p_count.

Every window has the length k = len(p), so build the first window's counts once, then slide one step at a time: add the letter entering on the right and remove the one leaving on the left. Each slide changes exactly two letters of the window, so updating window_count for s[i] and s[i - k] keeps it equal to the counts of the k characters ending at i without recounting the whole window, and window_count == p_count then says whether that window is an anagram of p. The trap is the index of the leaving letter: when s[i] enters, s[i - k] leaves; an off-by-one there shifts every window, so the windows compared are the wrong ones.

See it run

Fixed Window, step by step

i - · window_count empty · p_count a1 b1 c1 · result []

i 2 · window_count a1 b1 c1 · p_count a1 b1 c1 · result []

i 2 · window_count a1 b1 c1 · p_count a1 b1 c1 · result [0]

i 3 · window_count a1 b1 c1 · p_count a1 b1 c1 · result [0]

i 3 · window_count a1 b1 e1 · p_count a1 b1 c1 · result [0]

i 4 · window_count a1 b1 e1 · p_count a1 b1 c1 · result [0]

i 5 · window_count a1 b1 e1 · p_count a1 b1 c1 · result [0]

i 6 · window_count a1 b2 · p_count a1 b1 c1 · result [0]

i 7 · window_count a2 b1 · p_count a1 b1 c1 · result [0]

i 8 · window_count a1 b1 c1 · p_count a1 b1 c1 · result [0,6]

i 9 · window_count a1 c1 d1 · p_count a1 b1 c1 · result [0,6]

i - · window_count a1 c1 d1 · p_count a1 b1 c1 · result [0,6] · answer [0,6]

Each box is one letter of s, numbered by its index. The window always covers k letters.

  • enter the letter s[i] that the loop adds this step
  • leave the letter s[i - k] that the loop removes this step
  • stay a letter that is inside the window and is not compared as a match
  • match a letter of a window whose window_count equals p_count
  • not out the letter s[i - k + 1] that a wrong removal would take out

Start: s = "cbaebabacd" and p = "abc", so k = 3. Every window has 3 letters, and p_count holds the letters of p.

Build the first window s[0..2] = "cba" by adding its 3 letters. window_count is now a1 b1 c1.

window_count equals p_count, so the window starting at 0 is an anagram and 0 joins result.

Trap: when s[3] enters, the letter that leaves is s[i - k] = s[0], not s[i - k + 1] = s[1] = "b". That letter is still inside the window.

Add s[3] = "e" and remove s[0] = "c". window_count does not equal p_count.

Add s[4] = "b" and remove s[1] = "b". window_count does not equal p_count.

Add s[5] = "a" and remove s[2] = "a". window_count does not equal p_count.

Add s[6] = "b" and remove s[3] = "e". window_count does not equal p_count.

Add s[7] = "a" and remove s[4] = "b". window_count does not equal p_count.

Add s[8] = "c" and remove s[5] = "a". window_count equals p_count, so 6 joins result.

Add s[9] = "d" and remove s[6] = "b". window_count does not equal p_count.

Done: result = [0, 6], the start of every window that is an anagram of p.

0c
1b
2a
3e
4b
5a
6b
7a
8c
9d
1 / 12
Gotchas1 of 5

Gotcha 1 of 5

  1. 1
    The trap

    Gotcha 1: Removing s[i - k + 1] instead of s[i - k]

    Mistake
    window_count[ord(s[i - k + 1]) - ord('a')] -= 1
    Fix
    window_count[ord(s[i - k]) - ord('a')] -= 1

    What happens: Before the step, the window is s[i - k..i - 1]. When s[i] enters, the letter that leaves is s[i - k]. Removing s[i - k + 1] takes out a letter that is still in the window. The counts then match no real window. The result lists wrong start indices.

    Example: s = "cbaebabacd", p = "abc"
    Mistake: returns [0, 3, 4, 5]Fix: returns [0, 6]
  2. 2

    Gotcha 2: Writing the outgoing index as s[k - i] instead of s[i - k]

    Mistake
    window_count[ord(s[k - i]) - ord('a')] -= 1
    Fix
    window_count[ord(s[i - k]) - ord('a')] -= 1

    What happens: The loop runs i from k upward, and the letter that leaves at step i is s[i - k]. The index i - k grows by 1 at each step, because the window moves right. The index k - i does the opposite: it is 0 at i = k, then -1, -2 and so on. Python reads a negative index from the end of the string and raises no error. With s = "cbaebabacd" and k = 3, the first slide at i = 3 removes s[0], which is correct. At i = 4 the code removes s[-1], the last letter 'd', instead of s[1], the letter 'b'. The count of 'd' becomes -1 and the count of 'b' stays too high. From then on, window_count matches no real window, so no later window can equal p_count.

    Example: s = "cbaebabacd", p = "abc"
    Mistake: returns [0]Fix: returns [0, 6]
  3. 3

    Gotcha 3: Comparing between the add and the remove

    Mistake
    window_count[ord(s[i]) - ord('a')] += 1
    if window_count == p_count:
        result.append(i - k + 1)
    window_count[ord(s[i - k]) - ord('a')] -= 1
    Fix
    window_count[ord(s[i]) - ord('a')] += 1
    window_count[ord(s[i - k]) - ord('a')] -= 1
    if window_count == p_count:
        result.append(i - k + 1)

    What happens: After the add, the window holds k + 1 letters. p_count holds exactly k letters. So the comparison is always False at that point. Only the first window, checked before the loop, can still match.

    Example: s = "cbaebabacd", p = "abc"
    Mistake: returns [0]Fix: returns [0, 6]
  4. 4

    Gotcha 4: Dropping the len(s) < k check

    What happens: When s is shorter than p, no window of length k fits. In Python, s[:k] then returns the whole shorter string without an error. Its counts never equal p_count, so the result is still []. In Java or C++, reading k characters from a shorter string goes past its end. So return [] before the first window is built.

  5. 5

    Gotcha 5: Re-summing every window in Maximum Average Subarray I (LC 643)

    Mistake
    window_sum = sum(nums[i - k + 1:i + 1])
    Fix
    window_sum += nums[i] - nums[i - k]

    What happens: Maximum Average Subarray I (LC 643) slides a sum instead of counts. sum(nums[i - k + 1:i + 1]) reads k values at every step. The whole scan then costs O(N · k). Adding the entering value and subtracting the leaving one costs O(1) per step. Both give the same answer.

Not this one if…
  • No window size is given: you want the longest window that stays within a limit, as in Max Consecutive Ones II (LC 487). See Longest Window

Deep Dive

Why one letter in and one letter out is enough

A fixed window has the same length k at every position. The window that ends at index i is s[i - k + 1..i]. The next window ends at i + 1, and it is the same letters with two changes. The letter s[i - k + 1] is gone from the left side, and s[i + 1] is new on the right side.

So the card never counts a window again. It keeps window_count and changes two entries per step: +1 for the letter that enters and -1 for the letter that leaves. After that, window_count is the exact letter count of the new window.

In Find All Anagrams in a String (LC 438), two strings are anagrams when every letter has the same count in both. So the test for one window is window_count == p_count. The two lists have 26 entries each, so the comparison costs a fixed amount of work.

Counting every window again costs k steps per window. For len(s) = 100000 and k = 50000, there are 50001 windows, and the work is about 2,500,050,000 steps. The slide costs two updates and one comparison per window, so the work is proportional to len(s).

The slide traced on s = "cbaebabacd" and p = "abc"

Here k = 3, p_count is one a, one b and one c, and s has 10 letters, so there are 8 windows.

The first window is s[0..2] = "cba". Its counts equal p_count, so result starts as [0].

Each slide from i = 3 to i = 9 adds s[i] and removes s[i - 3]. The windows from start 1 to start 5 are bae, aeb, eba, bab and aba. None of them has the counts of abc.

The window at start 6 is bac. It is added at i = 8, when c enters and a leaves. Its counts equal p_count, so 6 joins result. The window at start 7 is acd, and it does not match. The answer is [0, 6].

The order inside one slide

Inside one slide, the comparison comes after both updates. The window holds k letters only after the add and the remove are both done. Between them it holds k + 1 letters, and p_count holds k letters, so a comparison there always fails.

The leaving index is i - k. At the first slide, i = k, so the leaving letter is s[0], the first letter of the previous window. The index i - k + 1 is the first letter of the new window. It must stay.

Before the first slide, the loop for i in range(k) builds the first window and the code records it. That window has no letter to remove, so it is handled before the slide loop.

If len(s) < k, no window fits, and the card returns [] before building anything.

The same slide with a sum, and with a yes or no answer

The template does not depend on letter counts. It only needs a state that an add call and a remove call can update in constant time.

In Maximum Average Subarray I (LC 643), the state is one number: window_sum += nums[i] - nums[i - k]. For nums = [1, 12, -5, -6, 50, 3] and k = 4, the best window sum is 51, so the answer is 51 / 4 = 12.75.

In Permutation in String (LC 567), the state is the same window_count. The question is only whether any window equals p_count. For s1 = "ab" and s2 = "eidbaooo", the window ba matches, so the answer is True. For s2 = "eidboaoo", no window matches, so the answer is False.

When the alphabet is large, comparing two lists is slow. Keep a counter mismatched of the letters whose counts differ, and update it when a count changes. A window matches when mismatched == 0.

60-Second Pitch

"The window length k is fixed, so I do not count each window again. I keep window_count, the letter counts of the current window. When I move one step right, s[i] enters and s[i - k] leaves. That is two small updates, and window_count stays exact for the k letters that end at i. I compare window_count with p_count only after both updates, because the window has k letters only then. The comparison has 26 entries, so it costs a fixed amount. The time is O(N) and the extra space is O(1) for 26 letters. The same template slides a sum for Maximum Average Subarray I."

Common Questions

Why is the work O(N) when each step compares two lists of 26 entries?

The list has 26 entries however long s is, so one comparison costs a fixed amount of work. There are about N windows, and each costs two updates and one comparison. The total is proportional to N.

Which letter leaves when s[i] enters, and why?

The letter s[i - k] leaves. Before the step, the window is s[i - k..i - 1]. After s[i] enters, the window must be s[i - k + 1..i], so only s[i - k] is outside it.

What changes if the alphabet is large, for example all Unicode letters?

Use a dictionary for the counts. Comparing two dictionaries costs about the number of different letters, so keep a number mismatched of the letters whose counts differ. Update it when a count changes. A window matches when mismatched is 0, and each step costs a fixed amount again.

How does the same template solve Maximum Average Subarray I (LC 643)?

The state is one sum. Each step does window_sum += nums[i] - nums[i - k] and updates the best sum. For [1, 12, -5, -6, 50, 3] with k = 4 the best sum is 51, so the average is 12.75.

When is a fixed window the wrong tool?

It is wrong when the window length is not given and the window must grow or shrink to keep a condition. That is the case in the Longest Window and the Shortest Window cards. It is also wrong when you need the maximum of each window and a sum does not give it. That case needs the Monotonic Deque card.

SWE Reasoning

Spot it

  • The window length k is given in the problem, or equals the length of another input.
  • The question is asked for every window: the best sum, a match with a pattern, a count of windows.
  • The state of the window can change in constant time when one element enters and one leaves (a sum, a letter count, a count of vowels).
  • Example problems: Find All Anagrams in a String (LC 438), Maximum Average Subarray I (LC 643), Permutation in String (LC 567).

The invariant, step by step

  1. Before the slide loop, window_count holds the counts of s[0..k-1], and the card compares it to p_count.
  2. At index i, s[i] enters, so its count rises by one.
  3. At the same index, s[i - k] leaves, so its count falls by one.
  4. Now the window is s[i - k + 1..i], and it holds exactly k letters.
  5. window_count equals the counts of those k letters, so window_count == p_count is true exactly when the window is an anagram of p.
  6. If it is true, the start index i - k + 1 joins result.

Traps

  • Removing s[i - k + 1]. That letter is still in the window. Every later count is wrong, and for "cbaebabacd" with "abc" the result is [0, 3, 4, 5] instead of [0, 6].
  • Comparing before the remove. The window has k + 1 letters at that point, so the comparison fails. Only the first window can still match, and the result is [0].
  • Writing s[k - i] for the leaving letter. The index falls as i grows. In Python it becomes negative and reads from the end of the string without an error.
  • Forgetting the case len(s) < k. In some languages, building the first window then reads past the end of the string.
  • Summing the whole window at every step. The answer is correct but the work is O(N * k) instead of O(N).

Practice

Canonical LeetCode:Find All Anagrams in a String (LC 438)Maximum Average Subarray I (LC 643)Maximum Number of Vowels in a Substring of Given Length (LC 1456)Number of Sub-arrays of Size K and Average Greater than or Equal to Threshold (LC 1343)Moving Average from Data Stream (LC 346)Minimum Swaps to Group All 1's Together (LC 1151)Permutation in String (LC 567)Contains Duplicate II (LC 219)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•