Create frequency arrays 't_count' and 'window_count' initialized to zero. Count frequencies of characters in t ('ABC') and store in 't_count'.
💡 This sets up character requirements for quick checks during window expansion.
Line:t_count = [0] * 128
window_count = [0] * 128
for c in t:
t_count[ord(c)] += 1
💡 Frequency arrays allow O(1) checks for character counts instead of hash maps.
setup
Initialize variables and pointers
Initialize 'have' and 'need' to track satisfied and required characters. Set 'res' to [-1, -1] and 'res_len' to infinity. Set 'left' pointer to 0.
💡 These variables track progress and store the best window found.
Line:have, need = 0, sum(1 for x in t_count if x > 0)
res, res_len = [-1, -1], float('inf')
left = 0
💡 'need' counts unique required characters; 'have' counts how many are currently met.
expand
Expand window: move right to 0, include 'A'
Move 'right' pointer to index 0 and include 'A'. Increment 'window_count' for 'A'. Since 'A' is required and now fully satisfied, increment 'have'.
💡 Expanding window adds characters and updates counts to check requirements.
Line:for right in range(len(s)):
c = s[right]
window_count[ord(c)] += 1
if t_count[ord(c)] > 0 and window_count[ord(c)] == t_count[ord(c)]:
have += 1
💡 When count of a required character matches needed count, 'have' increases.
expand
Expand window: move right to 1, include 'D'
Move 'right' to index 1 and include 'D'. 'D' is not required, so 'have' remains unchanged. Update 'window_count' for 'D'.
💡 Including non-required characters expands the window without affecting 'have'.
Line:c = s[right]
window_count[ord(c)] += 1
if t_count[ord(c)] > 0 and window_count[ord(c)] == t_count[ord(c)]:
have += 1
💡 Only required characters affect 'have'.
expand
Expand window: move right to 2, include 'O'
Move 'right' to index 2 and include 'O'. 'O' is not required, so 'have' remains 1. Update 'window_count' for 'O'.
💡 Window expands with non-required characters without changing 'have'.
Line:c = s[right]
window_count[ord(c)] += 1
if t_count[ord(c)] > 0 and window_count[ord(c)] == t_count[ord(c)]:
have += 1
💡 Non-required characters do not affect 'have'.
expand
Expand window: move right to 3, include 'B'
Move 'right' to index 3 and include 'B'. 'B' is required and now fully satisfied, so increment 'have' to 2.
💡 Adding a required character that meets needed count increases 'have'.
Line:c = s[right]
window_count[ord(c)] += 1
if t_count[ord(c)] > 0 and window_count[ord(c)] == t_count[ord(c)]:
have += 1
💡 Tracking 'have' helps know when window contains all required characters.
expand
Expand window: move right to 4, include 'E'
Move 'right' to index 4 and include 'E'. 'E' is not required, so 'have' remains 2. Update 'window_count' for 'E'.
💡 Window expands with non-required characters while searching for missing required ones.
Line:c = s[right]
window_count[ord(c)] += 1
if t_count[ord(c)] > 0 and window_count[ord(c)] == t_count[ord(c)]:
have += 1
💡 Non-required characters do not affect 'have'.
expand
Expand window: move right to 5, include 'C'
Move 'right' to index 5 and include 'C'. 'C' is required and now fully satisfied, so increment 'have' to 3, matching 'need'.
💡 Window now contains all required characters at least once.
Line:c = s[right]
window_count[ord(c)] += 1
if t_count[ord(c)] > 0 and window_count[ord(c)] == t_count[ord(c)]:
have += 1
💡 When 'have' equals 'need', the current window is valid and can be shrunk.
shrink
Shrink window: move left to 1, update counts
Window is valid. Current window length (6) is smaller than 'res_len' (inf), so update 'res' to [0,5] and 'res_len' to 6. Decrement count of 'A' at 'left' and move 'left' to 1. Since 'A' count in window is now less than required, decrement 'have' to 2.
💡 Shrinking window removes characters to find smaller valid substrings.
Line:while have == need:
if (right - left + 1) < res_len:
res = [left, right]
res_len = right - left + 1
window_count[ord(s[left])] -= 1
if t_count[ord(s[left])] > 0 and window_count[ord(s[left])] < t_count[ord(s[left])]:
have -= 1
left += 1
💡 Removing a required character below needed count invalidates the window.
expand
Expand window: move right to 6, include 'O'
Move 'right' to index 6 and include 'O'. 'O' is not required, so 'have' remains 2. Update 'window_count' for 'O'.
💡 Expanding window to regain all required characters after shrinking.
Line:c = s[right]
window_count[ord(c)] += 1
if t_count[ord(c)] > 0 and window_count[ord(c)] == t_count[ord(c)]:
have += 1
💡 Non-required characters do not affect 'have'.
expand
Expand window: move right to 7, include 'D'
Move 'right' to index 7 and include 'D'. 'D' is not required, so 'have' remains 2. Update 'window_count' for 'D'.
💡 Window continues expanding to find missing required characters.
Line:c = s[right]
window_count[ord(c)] += 1
if t_count[ord(c)] > 0 and window_count[ord(c)] == t_count[ord(c)]:
have += 1
💡 Non-required characters do not affect 'have'.
expand
Expand window: move right to 8, include 'E'
Move 'right' to index 8 and include 'E'. 'E' is not required, so 'have' remains 2. Update 'window_count' for 'E'.
💡 Window expands with non-required characters while searching for missing required ones.
Line:c = s[right]
window_count[ord(c)] += 1
if t_count[ord(c)] > 0 and window_count[ord(c)] == t_count[ord(c)]:
have += 1
💡 Non-required characters do not affect 'have'.
expand
Expand window: move right to 9, include 'B'
Move 'right' to index 9 and include 'B'. 'B' is required and window_count for 'B' increases but was already satisfied, so 'have' remains 2.
💡 Including extra occurrences of required characters does not increase 'have' beyond 'need'.
Line:c = s[right]
window_count[ord(c)] += 1
if t_count[ord(c)] > 0 and window_count[ord(c)] == t_count[ord(c)]:
have += 1
💡 Only the first time a required character count matches needed count increments 'have'.
expand
Expand window: move right to 10, include 'A'
Move 'right' to index 10 and include 'A'. 'A' count in window increases and matches required count again, so 'have' increments to 3, matching 'need'.
💡 Window now again contains all required characters, allowing shrinking.
Line:c = s[right]
window_count[ord(c)] += 1
if t_count[ord(c)] > 0 and window_count[ord(c)] == t_count[ord(c)]:
have += 1
💡 Window validity is regained after expanding.
shrink
Shrink window: move left to 2, update counts
Window is valid. Current window length (10) is not smaller than 'res_len' (6). Decrement count of 'D' at index 1 (not required), so 'have' stays 3. Move 'left' to 2.
💡 Continue shrinking to find smaller valid windows.
Line:while have == need:
if (right - left + 1) < res_len:
res = [left, right]
res_len = right - left + 1
window_count[ord(s[left])] -= 1
if t_count[ord(s[left])] > 0 and window_count[ord(s[left])] < t_count[ord(s[left])]:
have -= 1
left += 1
Window still valid. Current window length (9) is not smaller than 'res_len' (6). Decrement count of 'O' at index 2 (not required), so 'have' stays 3. Move 'left' to 3.
💡 Continue shrinking to find smaller valid windows.
Line:while have == need:
if (right - left + 1) < res_len:
res = [left, right]
res_len = right - left + 1
window_count[ord(s[left])] -= 1
if t_count[ord(s[left])] > 0 and window_count[ord(s[left])] < t_count[ord(s[left])]:
have -= 1
left += 1
Window still valid. Current window length (8) is not smaller than 'res_len' (6). Decrement count of 'B' at index 3 (required). Since window_count for 'B' becomes less than required, decrement 'have' to 2. Move 'left' to 4.
💡 Removing a required character below needed count invalidates the window.
Line:while have == need:
if (right - left + 1) < res_len:
res = [left, right]
res_len = right - left + 1
window_count[ord(s[left])] -= 1
if t_count[ord(s[left])] > 0 and window_count[ord(s[left])] < t_count[ord(s[left])]:
have -= 1
left += 1
💡 Window validity depends on all required characters meeting counts.
expand
Expand window: move right to 11, include 'N'
Move 'right' to index 11 and include 'N'. 'N' is not required, so 'have' remains 2. Update 'window_count' for 'N'.
💡 Expanding window to regain missing required characters.
Line:c = s[right]
window_count[ord(c)] += 1
if t_count[ord(c)] > 0 and window_count[ord(c)] == t_count[ord(c)]:
have += 1
💡 Non-required characters do not affect 'have'.
expand
Expand window: move right to 12, include 'C'
Move 'right' to index 12 and include 'C'. 'C' count in window increases and matches required count again, so increment 'have' to 3, matching 'need'.
💡 Window again contains all required characters, allowing shrinking.
Line:c = s[right]
window_count[ord(c)] += 1
if t_count[ord(c)] > 0 and window_count[ord(c)] == t_count[ord(c)]:
have += 1
💡 Window validity regained after expansion.
shrink
Shrink window to find minimum: move left to 9
Shrink window repeatedly while valid. Update 'res' to [9,12] with length 4, smaller than previous 6. Move 'left' from 4 to 9, decrementing counts for 'E', 'C', 'O', 'D', and 'E'. When removing 'B' at index 9, decrement 'have' to 2, invalidating window and stopping shrinking.
💡 Shrinking finds the smallest valid window 'BANC' from indices 9 to 12.
Line:while have == need:
if (right - left + 1) < res_len:
res = [left, right]
res_len = right - left + 1
window_count[ord(s[left])] -= 1
if t_count[ord(s[left])] > 0 and window_count[ord(s[left])] < t_count[ord(s[left])]:
have -= 1
left += 1
💡 Minimum window substring is found by shrinking the valid window as much as possible.
reconstruct
Return the minimum window substring
After iteration, extract substring from 'res' indices [9,12], which is 'BANC'. This is the shortest substring containing all characters from 'ABC'.
💡 Returning the final answer completes the algorithm.
Line:left, right = res
return s[left:right+1] if res_len != float('inf') else ""
💡 Algorithm efficiently finds minimum window substring without checking all substrings.
def min_window_optimized(s: str, t: str) -> str:
if not s or not t:
return "" # STEP 1
t_count = [0] * 128 # STEP 1
window_count = [0] * 128 # STEP 1
for c in t: # STEP 1
t_count[ord(c)] += 1
have, need = 0, sum(1 for x in t_count if x > 0) # STEP 2
res, res_len = [-1, -1], float('inf') # STEP 2
left = 0 # STEP 2
for right in range(len(s)): # STEP 3-20
c = s[right] # STEP 3-20
window_count[ord(c)] += 1 # STEP 3-20
if t_count[ord(c)] > 0 and window_count[ord(c)] == t_count[ord(c)]: # STEP 3-20
have += 1
while have == need: # STEP 9,15,16,17,20
if (right - left + 1) < res_len: # STEP 9,15,16,17,20
res = [left, right]
res_len = right - left + 1
window_count[ord(s[left])] -= 1
if t_count[ord(s[left])] > 0 and window_count[ord(s[left])] < t_count[ord(s[left])]:
have -= 1
left += 1
left, right = res # STEP 21
return s[left:right+1] if res_len != float('inf') else "" # STEP 21
📊
Minimum Window Substring - Watch the Algorithm Execute, Step by Step
Watching each pointer move and frequency update helps you understand how the algorithm efficiently finds the minimum window without checking every substring.
✓ The sliding window expands and shrinks dynamically to efficiently find the minimum substring containing all required characters.
This dynamic adjustment is hard to grasp from code alone but becomes clear when watching pointer movements and frequency updates.
✓ Tracking 'have' and 'need' counts allows quick checks of window validity without scanning the entire window each time.
Understanding this optimization clarifies why the algorithm runs in linear time.
✓ Shrinking the window as soon as all requirements are met ensures the minimum window is found, not just any valid window.
Seeing when and why the left pointer moves helps understand the minimality guarantee.
Practice
(1/5)
1. Consider the following code snippet implementing the optimal sliding window approach to find all anagrams of p in s. Given s = "abab" and p = "ab", what is the content of res after the loop completes?
easy
A. [0, 2]
B. [0, 1, 2]
C. [1, 2]
D. [0, 1]
Solution
Step 1: Initialize frequency counts for p = "ab" and first window in s = "ab"
p_count and s_count both have counts for 'a' and 'b' as 1, so matches = 26 initially except for other letters.
Step 2: Slide window over s and update res
At r=2, window is "ba" starting at index 1, matches == 26, append 0. At r=3, window is "ab" starting at index 2, matches == 26, append 1. After loop, append last index 2 if matches == 26. Final res = [0,1].
Final Answer:
Option D -> Option D
Quick Check:
Matches found at indices 0 and 1 for "ab" and "ba" [OK]
Hint: Sliding window updates res at matches == 26 [OK]
Common Mistakes:
Off-by-one errors in appending indices
Forgetting last window check
Miscounting matches after updates
2. You need to find the length of the longest substring in a string that contains at most two distinct characters. Which approach guarantees an optimal solution in terms of time complexity?
easy
A. Use a brute force approach checking all substrings with nested loops and counting distinct characters each time.
B. Use a sliding window with a frequency map to track character counts and adjust window size dynamically.
C. Use dynamic programming to store results of substrings and build up the solution.
D. Use a greedy approach that always picks the next character if it doesn't increase distinct count beyond two.
Solution
Step 1: Understand problem constraints
The problem requires the longest substring with at most two distinct characters, which suggests a variable-length window that expands and contracts based on distinct character count.
Step 2: Identify suitable algorithm
Sliding window with a frequency map efficiently tracks character counts and distinct characters, allowing O(n) time complexity by adjusting window boundaries dynamically.
Final Answer:
Option B -> Option B
Quick Check:
Sliding window with frequency map is the classic optimal approach for this problem [OK]
Hint: Sliding window tracks distinct chars in O(n) time [OK]
Common Mistakes:
Thinking brute force is efficient enough
Confusing DP with substring problems
Assuming greedy always works
3. What is the time complexity of the optimal sliding window algorithm that uses two frequency arrays and a match count to find all anagrams of p in s?
medium
A. O(n * m), where n = length of s and m = length of p
B. O(n * 26), where n = length of s
C. O(n), where n = length of s
D. O(n + m), where n = length of s and m = length of p
Solution
Step 1: Analyze the main loop over string s
The algorithm slides a window of size m = len(p) over s once, performing O(1) updates per step.
Step 2: Consider frequency array operations
Frequency arrays are fixed size 26, so comparisons and updates are O(1) per iteration, not dependent on m.
Final Answer:
Option C -> Option C
Quick Check:
Sliding window with fixed alphabet size -> O(n) time [OK]
Hint: Fixed alphabet size means frequency ops are O(1) [OK]
Common Mistakes:
Confusing frequency array size with input size
Assuming nested loops cause O(n*m)
Ignoring constant alphabet size
4. Suppose the array can contain negative numbers and you want to find all pivot indices (not just the first). Which modification to the optimal single-pass algorithm correctly finds all pivot indices?
hard
A. Run the single-pass algorithm once and return the first pivot index found.
B. Modify the single-pass algorithm to collect all indices where left_sum == total - left_sum - num instead of returning immediately.
C. Use prefix sums to precompute sums and then check each index in a separate loop.
D. Sort the array and use two pointers to find all pivot indices efficiently.
Solution
Step 1: Understand the requirement
We need all pivot indices, not just the first.
Step 2: Modify the single-pass approach
Instead of returning immediately when a pivot is found, collect the index and continue iterating.
Step 3: Avoid sorting or extra loops
Sorting breaks the order and two loops increase complexity unnecessarily.
Final Answer:
Option B -> Option B
Quick Check:
Collecting all valid indices in one pass is efficient and correct [OK]
Hint: Collect all pivots during single pass instead of early return [OK]
Common Mistakes:
Returning early and missing other pivots
Using sorting which breaks index order
Using nested loops increasing complexity
5. Suppose the input array can contain multiple repeated elements and you want to find the longest subarray with equal number of 0s and 1s, but now elements can be reused multiple times (like a circular array). Which modification to the prefix sum approach correctly handles this scenario?
hard
A. Use a sliding window with two pointers to find balanced subarrays in the circular array.
B. Reset prefix sums and first_occurrence after each full pass to handle reuse.
C. Extend the array by concatenating it to itself and run the prefix sum algorithm on the doubled array.
D. Modify prefix sums to be modulo n and track first occurrences modulo n.
Solution
Step 1: Recognize circular array means subarrays can wrap around.
Doubling the array simulates wrap-around by linearizing circular behavior.
Step 2: Run prefix sum algorithm on doubled array to find longest balanced subarray crossing boundary.
This preserves O(n) complexity and correctness.
Final Answer:
Option C -> Option C
Quick Check:
Other options break correctness or do not handle wrap-around properly [OK]
Hint: Double array to handle circular subarrays [OK]
Common Mistakes:
Resetting prefix sums loses info
Modulo prefix sums break balance detection
Sliding window can't detect all wrap-around cases
Recommended
Ready to prove it?
Pass the test and this pattern moves unknownstrongwhile earning +1 Grip toward your certificate.