Builds on Hash Map, which you haven’t finished yet.
Fixed-Size Sliding Window
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.
Fixed Window
i >= k -> (Absorb Incoming, Evict Outgoing)k is given and you measure every window: averages, counts, anagram matches.| 1 | from typing import Callable, List |
| 2 | Â |
| 3 | Â |
| 4 | def 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) |
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 emptyp_count a1 b1 c1result []
Each box is one letter of s, numbered by its index. The window always covers k letters.
enterthe letter s[i] that the loop adds this stepleavethe letter s[i - k] that the loop removes this stepstaya letter that is inside the window and is not compared as a matchmatcha letter of a window whose window_count equals p_countnot outthe 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.
Gotcha 1 of 5
- The trap
Gotcha 1: Removing
s[i - k + 1]instead ofs[i - k]Mistakewindow_count[ord(s[i - k + 1]) - ord('a')] -= 1Fixwindow_count[ord(s[i - k]) - ord('a')] -= 1What happens: Before the step, the window is
s[i - k..i - 1]. Whens[i]enters, the letter that leaves iss[i - k]. Removings[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]
- 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
sis, so one comparison costs a fixed amount of work. There are aboutNwindows, and each costs two updates and one comparison. The total is proportional toN.- Which letter leaves when
s[i]enters, and why? The letter
s[i - k]leaves. Before the step, the window iss[i - k..i - 1]. Afters[i]enters, the window must bes[i - k + 1..i], so onlys[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
mismatchedof the letters whose counts differ. Update it when a count changes. A window matches whenmismatchedis 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]withk = 4the 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
kis 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
- Before the slide loop,
window_countholds the counts ofs[0..k-1], and the card compares it top_count. - At index
i,s[i]enters, so its count rises by one. - At the same index,
s[i - k]leaves, so its count falls by one. - Now the window is
s[i - k + 1..i], and it holds exactlykletters. window_countequals the counts of thosekletters, sowindow_count == p_countis true exactly when the window is an anagram ofp.- If it is true, the start index
i - k + 1joinsresult.
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 + 1letters 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 asigrows. 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 ofO(N).
Practice
Builds on Hash Map, which you haven’t finished yet.
Fixed-Size Sliding Window
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.
Fixed Window
i >= k -> (Absorb Incoming, Evict Outgoing)k is given and you measure every window: averages, counts, anagram matches.| 1 | from typing import Callable, List |
| 2 | Â |
| 3 | Â |
| 4 | def 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) |
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 emptyp_count a1 b1 c1result []
Each box is one letter of s, numbered by its index. The window always covers k letters.
enterthe letter s[i] that the loop adds this stepleavethe letter s[i - k] that the loop removes this stepstaya letter that is inside the window and is not compared as a matchmatcha letter of a window whose window_count equals p_countnot outthe 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.
Gotcha 1 of 5
- The trap
Gotcha 1: Removing
s[i - k + 1]instead ofs[i - k]Mistakewindow_count[ord(s[i - k + 1]) - ord('a')] -= 1Fixwindow_count[ord(s[i - k]) - ord('a')] -= 1What happens: Before the step, the window is
s[i - k..i - 1]. Whens[i]enters, the letter that leaves iss[i - k]. Removings[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]
- 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
sis, so one comparison costs a fixed amount of work. There are aboutNwindows, and each costs two updates and one comparison. The total is proportional toN.- Which letter leaves when
s[i]enters, and why? The letter
s[i - k]leaves. Before the step, the window iss[i - k..i - 1]. Afters[i]enters, the window must bes[i - k + 1..i], so onlys[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
mismatchedof the letters whose counts differ. Update it when a count changes. A window matches whenmismatchedis 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]withk = 4the 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
kis 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
- Before the slide loop,
window_countholds the counts ofs[0..k-1], and the card compares it top_count. - At index
i,s[i]enters, so its count rises by one. - At the same index,
s[i - k]leaves, so its count falls by one. - Now the window is
s[i - k + 1..i], and it holds exactlykletters. window_countequals the counts of thosekletters, sowindow_count == p_countis true exactly when the window is an anagram ofp.- If it is true, the start index
i - k + 1joinsresult.
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 + 1letters 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 asigrows. 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 ofO(N).