Sliding Window
Look at a contiguous slice through a "window", and slide it along. When it moves, update your bookkeeping with only what came in and what went out. Never recount the middle.
1. What is this pattern for?
A window is a contiguous range nums[left..right]. The pattern keeps a small piece of state about the window (a sum, a character count, a set) and updates it in O(1) as the edges move. Each edge only ever moves forward, so every element enters once and leaves once: O(n) total instead of O(n²) or O(n·k).
- Fixed-size window: the size
kis given ("every window of size k", "anagram of p"). - Variable-size window: grow
rightevery step; shrinkleftas needed to find the longest or shortest window that satisfies a condition.
2. How it works (bisected)
- Pick the window state. What do you need to know about the slice to judge it? A running sum? A dict of character counts? The number of distinct characters?
- Expand: move
rightone step. Addnums[right]into the state. This happens on every loop iteration. - Shrink: move
leftwhile a rule says so.- Longest-valid problems: shrink while the window is invalid.
- Shortest-valid problems: shrink while the window is still valid (record the answer before each shrink).
- Fixed size: shrink exactly when the size exceeds
k.
nums[left]from the state, thenleft = left + 1. - Record the answer at the right moment (after shrinking for "longest", inside the shrink loop for "shortest").
- Why it's linear:
leftandrighteach move at mostntimes in total, even though there's awhileinside afor.
3. Seeing a problem: how to spot Sliding Window
Trigger words
- "contiguous subarray / substring"
- "longest / shortest / minimum length … such that …"
- "at most k distinct", "with no repeating characters", "after at most k replacements"
- "every window of size k", "maximum of each window", "anagram / permutation of p in s"
Shape of the input
- An array or string, and the answer is a contiguous piece of it.
- The condition is monotonic: making a valid window bigger keeps it valid (or making an invalid one smaller fixes it). That's why positives-only sums work and negatives don't.
- Brute force: "for every start, for every end, check the slice".
O(n²)or worse.
4. When to use it, and when not to
| Use Sliding Window when… | Prefer something else when… |
|---|---|
| Longest/shortest contiguous slice with a monotonic condition | Values can be negative and you need "sum = k": use Prefix Sum + hash map |
| Fixed-size windows (averages, anagrams, max per window) | The pair isn't contiguous (any two elements): use Two Pointers on a sorted array, or a hash map |
| "At most k distinct / k replacements" on strings | You need a subsequence (gaps allowed): use Dynamic Programming |
| Max/min of each window | Combine with a monotonic deque (see #239), a cousin of Monotonic Stack; for "k-th largest" use Top-K / heaps |
Cost: O(n) time (each index enters and leaves once). Space is O(1) for a sum, or O(σ) for a count map over an alphabet of size σ (often 26 or 128, so effectively constant).
5. The Python template (memorize this)
# A) Fixed size k: every window holds exactly k numbers
def max_sum_fixed(nums, k):
# Build the very first window by adding up the first k numbers
window_sum = 0
for i in range(k):
window_sum = window_sum + nums[i]
best_sum = window_sum # the first window is the best one seen so far
# Slide the window one step at a time: one number in, one number out
for right in range(k, len(nums)):
entering = nums[right] # new number sliding in on the right
leaving = nums[right - k] # old number sliding out on the left
window_sum = window_sum + entering - leaving
# Keep the bigger of "best so far" and "this window"
if window_sum > best_sum:
best_sum = window_sum
return best_sum
# B) Variable size, LONGEST valid window (the rule here: no repeated characters)
def longest_valid(s):
counts = {} # window state: character -> how many times it is in the window
left = 0 # left edge of the window
best_length = 0 # longest valid window found so far
for right in range(len(s)):
ch = s[right]
# 1. EXPAND: put s[right] into the window
if ch not in counts:
counts[ch] = 0
counts[ch] = counts[ch] + 1
# 2. SHRINK WHILE INVALID: ch now appears twice, so the window breaks the rule
while counts[ch] > 1:
left_ch = s[left]
counts[left_ch] = counts[left_ch] - 1 # take s[left] out of the window
left = left + 1 # move the left edge forward
# 3. RECORD: the window s[left..right] is valid again
window_length = right - left + 1
if window_length > best_length:
best_length = window_length
return best_length
# C) Variable size, SHORTEST valid window (the rule here: sum >= target)
def shortest_valid(nums, target):
left = 0 # left edge of the window
window_sum = 0 # sum of nums[left..right]
best_length = float("inf") # "infinity" means "no valid window found yet"
for right in range(len(nums)):
# 1. EXPAND: put nums[right] into the window
window_sum = window_sum + nums[right]
# 2. SHRINK WHILE VALID: record first, then try to make it shorter
while window_sum >= target:
window_length = right - left + 1
if window_length < best_length:
best_length = window_length
window_sum = window_sum - nums[left] # take nums[left] out
left = left + 1
# Never valid? The convention is to return 0
if best_length == float("inf"):
return 0
else:
return best_length
What the code is doing, line by line:
A) Fixed size k
window_sum = 0and the firstfor i in range(k)loop add upnums[0]…nums[k-1]: that is the very first window.best_sum = window_sum: the first window is automatically the best one we have seen so far.for right in range(k, len(nums)):rightis the index of the number that slides in. The number that slides out is exactlykplaces behind it, atright - k.window_sum = window_sum + entering - leaving: instead of re-adding allknumbers, we add one and subtract one. That's the whole trick.if window_sum > best_sum: remember the biggest window sum. At the end, return it.
B) Variable size, LONGEST valid window
countsis the window's state: for every character, how many copies are insides[left..right].leftstarts at 0 andbest_lengthat 0.for right in range(len(s)): every loop step grows the window by one character on the right.if ch not in counts: counts[ch] = 0thencounts[ch] = counts[ch] + 1: the standard two-step way to count with a plain dict (make the key exist, then add 1).while counts[ch] > 1: the new character is now a duplicate, so the window breaks the rule. Removes[left]from the counts and moveleftforward, again and again, until the duplicate is gone. It's awhile, not anif, because one step may not be enough.- After the loop the window is valid again, so measure
right - left + 1and keep it if it's the longest so far.
C) Variable size, SHORTEST valid window
best_length = float("inf"): "infinity" is a placeholder meaning "no valid window found yet"; any real length is smaller.- Each step adds
nums[right]towindow_sum(grow). while window_sum >= target: the window is valid. Record its length first (it might be the shortest), then subtractnums[left]and moveleftto try an even shorter window.- At the end, if
best_lengthis still infinity no window ever worked, so return 0; otherwise returnbest_length.
Dry run of template A on the example: nums = [2, 1, 5, 1, 3, 2], k = 3 → returns 9
| Step | Window | entering | leaving | window_sum | best_sum |
|---|---|---|---|---|---|
| first window | [2, 1, 5] | – | – | 8 | 8 |
| right = 3 | [1, 5, 1] | 1 | 2 | 7 | 8 |
| right = 4 | [5, 1, 3] | 3 | 1 | 9 | 9 |
| right = 5 | [1, 3, 2] | 2 | 5 | 6 | 9 |
Dry run of template B on the example: s = "abcabcbb" → returns 3
| right | ch | removed while shrinking | left | window | best_length |
|---|---|---|---|---|---|
| 0 | 'a' | – | 0 | 'a' | 1 |
| 1 | 'b' | – | 0 | 'ab' | 2 |
| 2 | 'c' | – | 0 | 'abc' | 3 |
| 3 | 'a' | 'a' | 1 | 'bca' | 3 |
| 4 | 'b' | 'b' | 2 | 'cab' | 3 |
| 5 | 'c' | 'c' | 3 | 'abc' | 3 |
| 6 | 'b' | 'a', 'b' | 5 | 'cb' | 3 |
| 7 | 'b' | 'c', 'b' | 7 | 'b' | 3 |
Template C is traced step by step in problem 209 below.
6. How to remember it forever
How this hook works: what “Caterpillar crawl” means
What the slogan means, phrase by phrase
- “Caterpillar”
- The window: a contiguous stretch
s[left..right]with a head and a tail. - “The head always stretches forward”
rightmoves one step every round of theforloop. Every element gets eaten once.- “the tail scrunches up only when it has to”
leftmoves only inside awhileloop, when the window has broken the rule (a repeated letter, a sum too big).- “never walks backwards”
- Neither end ever moves left. Each element enters once and leaves once, so the whole scan is
O(n).
Why this picture fits the pattern: Sliding-window problems ask for the best contiguous stretch. Restarting from every position is O(n²). A caterpillar keeps what's already inside its body and only adjusts the two ends.
The story → the code
| In the story | In the code |
|---|---|
| The caterpillar's body | the window s[left..right] |
| Head steps forward | for right in range(len(s)) |
| What's in its stomach | the window state: counts, window_sum |
| Tail scrunches up | while window is invalid: remove s[left], left += 1 |
| Longest body so far | best = max(best, right - left + 1) |
Watch it on numbers
Longest stretch without a repeated letter in "abcabcbb".
- Head eats a, b, c. The body is
abc, length 3. Best = 3. - Head eats a. Now there are two a's, so the tail scrunches past the first a. Body
bca, length 3. - Head eats b: tail scrunches past the old b →
cab. Head eats c: tail scrunches →abc. - Head eats b: the tail has to move twice (past a, then past b) →
cb. Head eats the last b →b. - The head moved 8 times and the tail 7 times, never backwards. Answer 3.
The trap it saves you from
Resetting the window when it breaks (back to O(n²)), or shrinking with if instead of while, which removes only one letter when the window may need several.
It still works when…
"Longest: shrink while bad. Shortest: shrink while good." For the shortest subarray with sum ≥ target, the tail scrunches while the body is still valid, recording the length each time. For a fixed size k, head and tail move together: one in, one out.
Picture
A train window: one house slides in on the right, one slides out on the left. Your notebook only changes by those two houses.
Chant
"Grow right, fix left, record the best."
Longest vs shortest
"Longest: shrink while bad. Shortest: shrink while good."
7. LeetCode problems, solved step by step
Medium 209. Minimum Size Subarray Sum Google Meta Amazon Microsoft
Problem: Given positive integers nums and target, return the length of the shortest contiguous subarray whose sum is ≥ target (0 if none).
Solve it on LeetCode ↗ · Video walkthrough ↗
target = 7, nums = [2,3,1,2,4,3]2target = 4, nums = [1,4,4]1Constraints: 1 ≤ target ≤ 10⁹ · 1 ≤ nums.length ≤ 10⁵ · 1 ≤ nums[i] ≤ 10⁴
Hint 1
All numbers are positive, so growing a window only increases its sum and shrinking only decreases it.
Hint 2
Expand the right edge; while the sum is ≥ target, record the length and shrink from the left.
From brute force to the pattern: Every start with every end is O(n²). Because values are positive, once a window reaches the target there's no point extending it further: shrink it from the left instead. Each index enters and leaves the window once: O(n).
Spot it: "shortest contiguous" + "sum ≥" + all positive. Adding makes the sum bigger, removing makes it smaller, so the condition is monotonic: shortest-valid window.
Key insight: as soon as the window is valid, record it and keep shrinking from the left while it stays valid. That squeezes out the shortest window ending at each right.
Approach:
left = 0,window_sum = 0,best_length = ∞.- For each
right, addnums[right]towindow_sum. - While
window_sum ≥ target: recordright − left + 1, subtractnums[left], thenleft = left + 1. - Return
best_length, or 0 if the window never became valid.
from typing import List
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
left = 0 # left edge of the window
window_sum = 0 # sum of nums[left..right]
best_length = float("inf") # infinity = "no valid window found yet"
for right in range(len(nums)):
# Grow: bring nums[right] into the window
window_sum = window_sum + nums[right]
# The window is valid (sum >= target): record its length,
# then drop nums[left] to see if a shorter window still works
while window_sum >= target:
window_length = right - left + 1
if window_length < best_length:
best_length = window_length
window_sum = window_sum - nums[left]
left = left + 1
# If no window ever reached the target, LeetCode wants 0
if best_length == float("inf"):
return 0
else:
return best_length
What the code is doing, line by line:
left = 0,window_sum = 0: the window starts empty at the left end.best_length = float("inf")means "nothing found yet".for right in range(len(nums)): move the right edge over every index, one at a time.window_sum = window_sum + nums[right]: the new number joins the window.while window_sum >= target: the window is good enough. Computewindow_length = right - left + 1and keep it if it's smaller thanbest_length.- Still inside the
while: subtractnums[left]and doleft = left + 1. This shrinks the window from the left; the loop keeps going as long as the smaller window still reaches the target. - After the
forloop: ifbest_lengthis still infinity, no window ever reached the target, so return 0. Otherwise returnbest_length.
Dry run on the example: target = 7, nums = [2, 3, 1, 2, 4, 3] → returns 2
| right | nums[right] | window_sum after adding | lengths recorded while shrinking | left after | window_sum after | best_length |
|---|---|---|---|---|---|---|
| 0 | 2 | 2 | – | 0 | 2 | ∞ |
| 1 | 3 | 5 | – | 0 | 5 | ∞ |
| 2 | 1 | 6 | – | 0 | 6 | ∞ |
| 3 | 2 | 8 | 4 | 1 | 6 | 4 |
| 4 | 4 | 10 | 4, 3 | 3 | 6 | 3 |
| 5 | 3 | 9 | 3, 2 | 5 | 3 | 2 |
Edge cases to test:
- No window reaches the target → 0.
- A single element ≥ target → 1.
- The whole array is needed.
Complexity: O(n) time, O(1) space.
Medium 3. Longest Substring Without Repeating Characters Start here Google Meta Amazon Cisco Microsoft Apple Netflix
Problem: Length of the longest substring of s with all distinct characters.
Solve it on LeetCode ↗ · Video walkthrough ↗
s = "abcabcbb"3s = "bbbbb"1Constraints: 0 ≤ s.length ≤ 10⁵ · s consists of English letters, digits, symbols and spaces.
Hint 1
Grow a window to the right. What breaks it?
Hint 2
When the new character already appears inside the window, move the left edge just past its previous position (remember last positions in a map).
From brute force to the pattern: Checking every substring for repeats is O(n³) (or O(n²) with a set per start). A window that only moves forward, plus a map of each character's last index, finds the longest valid substring in one pass: O(n).
Spot it: "longest substring" + a rule ("no repeats") that a shorter window can only satisfy more easily. The second visualizer above runs exactly this.
Key insight (optimised): store the last index of each character. When you see a repeat inside the window, jump left straight past the old copy instead of shrinking one step at a time.
Approach:
- Keep
last_seen[ch]= the last index wherechappeared, andleft= the start of the window. - For each
right: ifs[right]was last seen at an index ≥left, jumpleftto that index + 1. - Set
last_seen[ch] = right. - Record
right − left + 1as a candidate.
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
last_seen = {} # character -> last index where we saw it
left = 0 # left edge of the window
best_length = 0 # longest window without repeats so far
for right in range(len(s)):
ch = s[right]
# Have we seen ch before, and is that old copy INSIDE the window?
if ch in last_seen:
previous_index = last_seen[ch]
if previous_index >= left:
# Jump left just past the old copy, so ch appears only once
left = previous_index + 1
# Remember the newest position of ch
last_seen[ch] = right
# The window s[left..right] has no repeats now: measure it
window_length = right - left + 1
if window_length > best_length:
best_length = window_length
return best_length
What the code is doing, line by line:
last_seenis a dict: character → the last index where we saw it.leftis the start of the window;best_lengthis the answer so far.for right in range(len(s))andch = s[right]: look at each character in turn.if ch in last_seen: we have met this character before. Read its old position intoprevious_index.if previous_index >= left: the old copy is inside the current window, so the window now has a repeat. Jumplefttoprevious_index + 1, just past the old copy. (If the old copy is to the left ofleft, it's already outside the window and we must not moveleftbackwards: try"abba".)last_seen[ch] = right: remember the newest position ofch.window_length = right - left + 1: the window has no repeats now, so its length is a candidate. Keep the largest inbest_length.
Dry run on the example: s = "pwwkew" → returns 3
| right | ch | repeat inside window? | left | window | best_length |
|---|---|---|---|---|---|
| 0 | 'p' | – | 0 | 'p' | 1 |
| 1 | 'w' | – | 0 | 'pw' | 2 |
| 2 | 'w' | yes, old copy at 1 → left = 2 | 2 | 'w' | 2 |
| 3 | 'k' | – | 2 | 'wk' | 2 |
| 4 | 'e' | – | 2 | 'wke' | 3 |
| 5 | 'w' | yes, old copy at 2 → left = 3 | 3 | 'kew' | 3 |
Edge cases to test:
- Empty string → 0.
- All the same character, e.g.
"bbbb"→ 1. - A repeat that's before the current window, e.g.
"abba"(don't move the left edge backwards).
Complexity: O(n) time, O(min(n, σ)) space.
Medium 424. Longest Repeating Character Replacement Google Meta Amazon Microsoft
Problem: You may change at most k characters of uppercase string s. Return the length of the longest substring that can be made of one repeated letter.
Solve it on LeetCode ↗ · Video walkthrough ↗
s = "ABAB", k = 24s = "AABABBA", k = 14Constraints: 1 ≤ s.length ≤ 10⁵ · s consists of only uppercase English letters. · 0 ≤ k ≤ s.length
Hint 1
A window can be made of one letter if window length − count of its most frequent letter ≤ k.
Hint 2
Grow the window; when that condition fails, slide the left edge forward by one.
From brute force to the pattern: Trying every substring and every target letter is O(26 · n²). A sliding window with letter counts checks validity in O(1) (length − max count ≤ k), so one pass suffices: O(n).
Spot it: "longest substring" + "at most k changes". A window is fixable if window_length − count_of_most_common_letter ≤ k.
Key insight: max_freq never needs to decrease. The answer only grows when a window with a higher max_freq appears, so a stale (too high) value just keeps the window from shrinking. It never produces a wrong answer.
Approach:
- Keep letter counts for the window and
max_freq, the highest count seen so far. - Expand
right; update its count andmax_freq. - While
window length − max_freq > k(too many letters to repaint), removes[left]and moveleft. - Record the window length.
class Solution:
def characterReplacement(self, s: str, k: int) -> int:
counts = {} # letter -> how many times it is in the window
left = 0 # left edge of the window
max_freq = 0 # biggest count of one letter seen in any window
best_length = 0 # answer so far
for right in range(len(s)):
ch = s[right]
# Add s[right] to the window's letter counts
if ch not in counts:
counts[ch] = 0
counts[ch] = counts[ch] + 1
# Did this letter become the most common one?
if counts[ch] > max_freq:
max_freq = counts[ch]
# Letters to repaint = window length - count of the most common letter.
# If that is more than k, the window is too wide: shrink it.
window_length = right - left + 1
while window_length - max_freq > k:
left_ch = s[left]
counts[left_ch] = counts[left_ch] - 1 # s[left] leaves the window
left = left + 1
window_length = right - left + 1
# The window can be fixed with at most k changes: record it
if window_length > best_length:
best_length = window_length
return best_length
What the code is doing, line by line:
countsholds how many times each letter is in the window.max_freqis the biggest single-letter count we have seen.- For each
right: adds[right]tocounts(create the key with 0 first if needed), and if its count beatsmax_freq, updatemax_freq. window_length - max_freqis how many letters we would have to repaint so the whole window is the most common letter.while window_length - max_freq > k: too many repaints needed. Takes[left]out ofcounts, moveleftforward, and recomputewindow_length.- We never lower
max_freqwhen letters leave. That's on purpose: the answer can only get bigger when some window has a highermax_freq, so an old (too high) value never gives a wrong answer. - After shrinking, the window can be fixed with at most
kchanges, so comparewindow_lengthwithbest_length.
Dry run on the example: s = "AABABBA", k = 1 → returns 4
| right | ch | counts (after) | max_freq | shrink? | left | window | best_length |
|---|---|---|---|---|---|---|---|
| 0 | 'A' | {'A': 1} | 1 | – | 0 | 'A' | 1 |
| 1 | 'A' | {'A': 2} | 2 | – | 0 | 'AA' | 2 |
| 2 | 'B' | {'A': 2, 'B': 1} | 2 | – | 0 | 'AAB' | 3 |
| 3 | 'A' | {'A': 3, 'B': 1} | 3 | – | 0 | 'AABA' | 4 |
| 4 | 'B' | {'A': 2, 'B': 2} | 3 | drop 'A' | 1 | 'ABAB' | 4 |
| 5 | 'B' | {'A': 1, 'B': 3} | 3 | drop 'A' | 2 | 'BABB' | 4 |
| 6 | 'A' | {'A': 2, 'B': 2} | 3 | drop 'B' | 3 | 'ABBA' | 4 |
Edge cases to test:
klarge enough to change everything → the whole length.k = 0→ the longest run of one letter.- A single character.
Complexity: O(n) time, O(26) space.
Medium 438. Find All Anagrams in a String Google Meta Amazon Microsoft
Problem: Return every start index in s where a substring is an anagram (same letters, any order) of p.
s = "cbaebabacd", p = "abc"[0,6]s = "abab", p = "ab"[0,1,2]Constraints: 1 ≤ s.length, p.length ≤ 3 * 10⁴ · s and p consist of lowercase English letters.
Hint 1
An anagram of p is any window of length len(p) with the same letter counts.
Hint 2
Slide a fixed-size window and update the counts in O(1) per step: add the new letter, remove the one leaving.
From brute force to the pattern: Sorting or counting every window from scratch is O(n · m). A fixed window whose counts are updated incrementally compares in O(26), so O(n) overall.
Spot it: an anagram of p has length len(p), so this is a fixed-size window. Compare letter counts.
Approach:
- If
pis longer thans, return[]. - Count the letters of
pinto a dictneed;havewill hold the counts of the current window ofs. - Slide one step at a time: add the new letter; once the window is longer than
len(p), remove the one falling out (deleting zero counts). - Whenever
have == need, record the window's start index.
from typing import List
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
window_size = len(p) # an anagram of p has exactly len(p) letters
result = []
if window_size > len(s):
return result # s is too short to hold an anagram of p
# need: letter -> how many times it appears in p
need = {}
for ch in p:
if ch not in need:
need[ch] = 0
need[ch] = need[ch] + 1
# have: letter -> how many times it appears in the current window of s
have = {}
for right in range(len(s)):
# One letter slides IN on the right
entering = s[right]
if entering not in have:
have[entering] = 0
have[entering] = have[entering] + 1
# Once the window would be too big, one letter slides OUT on the left
if right >= window_size:
leaving = s[right - window_size]
have[leaving] = have[leaving] - 1
if have[leaving] == 0:
# Delete zero counts so "have == need" compares fairly
del have[leaving]
# Same letters with the same counts = anagram; remember where it starts
if have == need:
start = right - window_size + 1
result.append(start)
return result
What the code is doing, line by line:
window_size = len(p): an anagram ofpalways has exactly that many letters. Ifsis shorter thanp, return the emptyresultright away.- The first loop fills
need: letter → how many times it appears inp. havewill hold the letter counts of the current window ofs. For eachright, the letters[right]slides in and its count goes up by 1.if right >= window_size: the window would now be one letter too long, so the letter atright - window_sizeslides out and its count goes down by 1. If that count hits 0 wedelthe key, otherwise{'a': 1, 'b': 0}would not equal{'a': 1}.if have == need: same letters with the same counts means the window is an anagram. It coverss[right - window_size + 1 .. right], so append that start index toresult.- (During the first
window_size - 1steps the window is still too short to match, which is fine.)
Dry run on the example: s = "cbaebabacd", p = "abc", so need = {'a': 1, 'b': 1, 'c': 1} → returns [0, 6]
| right | entering | leaving | window | have (after) | have == need? | result |
|---|---|---|---|---|---|---|
| 0 | 'c' | – | 'c' | {'c': 1} | no | [] |
| 1 | 'b' | – | 'cb' | {'c': 1, 'b': 1} | no | [] |
| 2 | 'a' | – | 'cba' | {'c': 1, 'b': 1, 'a': 1} | yes | [0] |
| 3 | 'e' | 'c' | 'bae' | {'b': 1, 'a': 1, 'e': 1} | no | [0] |
| 4 | 'b' | 'b' | 'aeb' | {'b': 1, 'a': 1, 'e': 1} | no | [0] |
| 5 | 'a' | 'a' | 'eba' | {'b': 1, 'a': 1, 'e': 1} | no | [0] |
| 6 | 'b' | 'e' | 'bab' | {'b': 2, 'a': 1} | no | [0] |
| 7 | 'a' | 'b' | 'aba' | {'b': 1, 'a': 2} | no | [0] |
| 8 | 'c' | 'a' | 'bac' | {'b': 1, 'a': 1, 'c': 1} | yes | [0, 6] |
| 9 | 'd' | 'b' | 'acd' | {'a': 1, 'c': 1, 'd': 1} | no | [0, 6] |
Edge cases to test:
plonger thans→[].- Overlapping matches, e.g.
s = "abab",p = "ab"→[0, 1, 2]. - Repeated letters in
p.
Complexity: O(n · 26) = O(n) time, O(26) space.
Hard 76. Minimum Window Substring Google Meta Amazon Cisco Microsoft
Problem: Return the shortest substring of s containing every character of t (with multiplicity). Return "" if none.
Solve it on LeetCode ↗ · Video walkthrough ↗
s = "ADOBECODEBANC", t = "ABC""BANC"s = "a", t = "a""a"Constraints: m == s.length · n == t.length · 1 ≤ m, n ≤ 10⁵ · s and t consist of uppercase and lowercase English letters.
Hint 1
Expand the right edge until the window contains every character of t (with multiplicity).
Hint 2
Then shrink from the left as far as possible while it stays valid; record the smallest window. A counter of "letters still missing" makes the validity check O(1).
From brute force to the pattern: Checking every substring for containing t is O(n² · m). A variable window that expands until valid and then shrinks greedily visits each index at most twice: O(n + m).
Spot it: "minimum window" + "contains all of". Adding characters can only help coverage, so it's a shortest-valid window.
Key insight: keep need[c] = how many more of c the window still needs (can go negative for extras), and a single integer missing so the validity check is O(1).
Approach:
- Count the letters of
tinto a dictneed; setmissing = len(t). - Expand
right: ifneed[ch] > 0the character was useful, somissinggoes down by 1. Always lowerneed[ch]by 1. - While
missing == 0(the window coverst): record it if it's the shortest so far, give backs[left](itsneedgoes up by 1), and if that count turned positive,missinggoes up by 1. Moveleft. - Return the best slice, or
"".
class Solution:
def minWindow(self, s: str, t: str) -> str:
# need[ch] = how many more copies of ch the window still needs.
# It can drop below zero when the window holds extra copies.
need = {}
for ch in t:
if ch not in need:
need[ch] = 0
need[ch] = need[ch] + 1
missing = len(t) # characters of t not yet covered by the window
left = 0 # left edge of the window
best_left = 0 # best window found so far is s[best_left..best_right]
best_right = -1 # -1 means "no valid window found yet"
for right in range(len(s)):
ch = s[right]
if ch not in need:
need[ch] = 0 # a letter t does not contain starts at 0
if need[ch] > 0:
missing = missing - 1 # this copy was really needed
need[ch] = need[ch] - 1 # the window now holds one more ch
# Every character of t is covered: shrink while that stays true
while missing == 0:
window_length = right - left + 1
best_length = best_right - best_left + 1
if best_right == -1 or window_length < best_length:
best_left = left # new shortest window: remember it
best_right = right
left_ch = s[left]
need[left_ch] = need[left_ch] + 1 # give s[left] back
if need[left_ch] > 0:
missing = missing + 1 # we just lost a character we needed
left = left + 1
# Slicing with best_right = -1 gives "" (nothing found)
return s[best_left:best_right + 1]
What the code is doing, line by line:
- Build
needfromt: letter → how many copies we still need.missing = len(t)counts how many characters oftare not yet covered. best_left = 0,best_right = -1: the best window iss[best_left..best_right];-1means nothing found yet.- For each
right: ifchis a lettertdoesn't have, give it a count of 0 so the dict lookups work. if need[ch] > 0: we still needed this letter, so one fewer character is missing. Thenneed[ch] = need[ch] - 1in every case (extra copies push the count below 0).while missing == 0: the window contains all oft. If it's the first valid window or shorter than the best one, saveleftandrightas the new best.- Still in the
while: gives[left]back (need[left_ch] + 1). If its count turned positive, we just lost a character we really needed, somissinggoes up and the loop will stop. Moveleftforward. - Finally return the slice
s[best_left:best_right + 1]. If nothing was found,best_rightis-1and the slices[0:0]is the empty string.
Dry run on the example: s = "ADOBECODEBANC", t = "ABC" → returns "BANC"
| right | ch | valid windows checked while shrinking | missing after | left after | best window |
|---|---|---|---|---|---|
| 0 | 'A' | – | 2 | 0 | (none yet) |
| 1 | 'D' | – | 2 | 0 | (none yet) |
| 2 | 'O' | – | 2 | 0 | (none yet) |
| 3 | 'B' | – | 1 | 0 | (none yet) |
| 4 | 'E' | – | 1 | 0 | (none yet) |
| 5 | 'C' | "ADOBEC" (new best) | 1 | 1 | "ADOBEC" |
| 6 | 'O' | – | 1 | 1 | "ADOBEC" |
| 7 | 'D' | – | 1 | 1 | "ADOBEC" |
| 8 | 'E' | – | 1 | 1 | "ADOBEC" |
| 9 | 'B' | – | 1 | 1 | "ADOBEC" |
| 10 | 'A' | "DOBECODEBA", "OBECODEBA", "BECODEBA", "ECODEBA", "CODEBA" | 1 | 6 | "ADOBEC" |
| 11 | 'N' | – | 1 | 6 | "ADOBEC" |
| 12 | 'C' | "ODEBANC", "DEBANC", "EBANC" (new best), "BANC" (new best) | 1 | 10 | "BANC" |
Edge cases to test:
tlonger thans→"".- Duplicate letters in
t, e.g.t = "aa". - The answer is the whole of
s.
Complexity: O(|s| + |t|) time, O(σ) space.
Hard 239. Sliding Window Maximum Google Meta Amazon Cisco Microsoft
Problem: For every window of size k, report its maximum.
nums = [1,3,-1,-3,5,3,6,7], k = 3[3,3,5,5,6,7]nums = [1], k = 1[1]Constraints: 1 ≤ nums.length ≤ 10⁵ · -10⁴ ≤ nums[i] ≤ 10⁴ · 1 ≤ k ≤ nums.length
Hint 1
Recomputing max for each window is O(k). Which elements can never be a future maximum?
Hint 2
Keep a deque of indices with decreasing values: drop smaller ones from the back when a bigger value arrives, and drop the front when it leaves the window.
From brute force to the pattern: Taking max of every window is O(n · k). A monotonic deque keeps only candidates that could still become a maximum; each index is added and removed once, so O(n).
Spot it: fixed-size window, but "max" can't be undone by subtraction. Keep a monotonic deque of indices whose values decrease front→back; the front is the window max.
Key insight: when a bigger value arrives, everything smaller behind it can never be a maximum again (it's older and smaller), so pop it.
Approach:
- Keep a deque of indices whose values decrease from front to back.
- For each
i: pop from the back while the back's value ≤nums[i], then pushi. - If the front index has slid out of the window (≤
i − k), pop it from the front. - Once
i ≥ k − 1, the front holds the window maximum; append it.
from collections import deque
from typing import List
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
# A deque ("double-ended queue") is a list that is fast at BOTH ends:
# append() adds on the right, pop() removes from the right,
# popleft() removes from the left.
# Here it stores INDICES whose values get smaller from front to back,
# so the front index always points at the current window's maximum.
window_indices = deque()
result = []
for i in range(len(nums)):
value = nums[i]
# Throw away indices from the back whose values are <= value:
# they are older AND not bigger, so they can never be a maximum again
while len(window_indices) > 0 and nums[window_indices[-1]] <= value:
window_indices.pop()
window_indices.append(i)
# The window is nums[i - k + 1 .. i]; if the front index is
# older than that, it has slid out, so remove it
if window_indices[0] <= i - k:
window_indices.popleft()
# From the first full window onwards, record the maximum (the front)
if i >= k - 1:
result.append(nums[window_indices[0]])
return result
What the code is doing, line by line:
from collections import deque: adeque("deck", double-ended queue) behaves like a list but adding/removing at both ends is fast. We useappend()andpop()on the right end andpopleft()on the left end.window_indicesstores indices (not values), ordered so their values get smaller from front to back. Sonums[window_indices[0]]is always the biggest value in the window.- For each
i:whilethe value at the back is<= value, pop it. Those numbers are older and not bigger than the new one, so they can never be a window maximum again. window_indices.append(i): the new index joins at the back.if window_indices[0] <= i - k: the window isnums[i - k + 1 .. i]; an index at or beforei - khas slid out, so remove it from the front.if i >= k - 1: from indexk - 1on, a full window exists, so append its maximumnums[window_indices[0]]toresult.
Dry run on the example: nums = [1, 3, -1, -3, 5, 3, 6, 7], k = 3 → returns [3, 3, 5, 5, 6, 7]
| i | value | popped from back (values) | popped from front | window_indices after | result |
|---|---|---|---|---|---|
| 0 | 1 | – | – | [0] (values [1]) | [] |
| 1 | 3 | 1 | – | [1] (values [3]) | [] |
| 2 | -1 | – | – | [1, 2] (values [3, -1]) | [3] |
| 3 | -3 | – | – | [1, 2, 3] (values [3, -1, -3]) | [3, 3] |
| 4 | 5 | -3, -1, 3 | – | [4] (values [5]) | [3, 3, 5] |
| 5 | 3 | – | – | [4, 5] (values [5, 3]) | [3, 3, 5, 5] |
| 6 | 6 | 3, 5 | – | [6] (values [6]) | [3, 3, 5, 5, 6] |
| 7 | 7 | 6 | – | [7] (values [7]) | [3, 3, 5, 5, 6, 7] |
Edge cases to test:
k = 1→ the array itself.k = n→ one value, the overall maximum.- Decreasing input, so the deque grows to size
k.
Complexity: O(n) time (each index pushed and popped once), O(k) space.
8. Common mistakes
if instead of while when shrinkingOne step of shrinking is often not enough. Variable windows shrink in a while loop.nums[left].[left..right] is right − left + 1.left if the previous copy is inside the window (last_seen[ch] >= left). Otherwise left can move backwards (try "abba").9. Self-check quiz
In a "shortest valid window" problem, where do you update the answer?
Longest subarray with sum exactly k, values may be negative.
"Does s2 contain a permutation of s1?" (LeetCode 567)
s1 has exactly len(s1) letters, so slide a window of that size and compare counts.A for loop over right with a while loop moving left inside. Total time?
left only moves forward, at most n times over the whole run. Amortised, the inner loop is O(1) per step.10. Practice list (in order)
| # | Problem | Level | Data structure | Twist |
|---|---|---|---|---|
| 643 | Maximum Average Subarray I | Easy | Warm-up: fixed window sum | |
| 121 | Best Time to Buy and Sell Stock | Easy | Window = [buy day, sell day]; jump buy to any cheaper day | |
| 219 | Contains Duplicate II | Easy | Fixed window of the last k values, kept in a set | |
| 3 | Longest Substring Without Repeating Characters | Medium | Longest valid; last-index jump | |
| 209 | Minimum Size Subarray Sum | Medium | Shortest valid | |
| 1004 | Max Consecutive Ones III | Medium | At most k zeros inside | |
| 424 | Longest Repeating Character Replacement | Medium | length − max_freq ≤ k | |
| 567 | Permutation in String | Medium | Fixed window + counts | |
| 438 | Find All Anagrams in a String | Medium | Fixed window, collect all starts | |
| 904 | Fruit Into Baskets | Medium | At most 2 distinct | |
| 1658 | Minimum Operations to Reduce X to Zero | Medium | Invert: longest middle window with sum total − x | |
| 395 | Longest Substring with At Least K Repeating Characters | Medium | Fix the number of distinct letters (1..26) so the window becomes monotonic | |
| 413 | Arithmetic Slices | Medium | Grow the current arithmetic run; each extension adds run − 2 new slices | |
| 713 | Subarray Product Less Than K | Medium | Variable window; every right edge adds right − left + 1 subarrays | |
| 718 | Maximum Length of Repeated Subarray | Medium | Slide one array over the other; count the longest run of matches on each diagonal | |
| 1343 | Number of Sub-arrays of Size K and Average Greater than or Equal to Threshold | Medium | Fixed window sum compared with k × threshold | |
| 1358 | Number of Substrings Containing All Three Characters | Medium | Shrink to the shortest valid window; every start up to left works | |
| 1423 | Maximum Points You Can Obtain from Cards | Medium | Taking k from the ends = leaving a middle window of n − k: minimise that window | |
| 1461 | Check If a String Contains All Binary Codes of Size K | Medium | Fixed window of length k with a rolling bit mask; collect distinct codes | |
| 1695 | Maximum Erasure Value | Medium | Longest-unique-window, but maximising the sum | |
| 1838 | Frequency of the Most Frequent Element | Medium | Sort; the window can be raised to its maximum if max × size − sum ≤ k | |
| 1888 | Minimum Number of Flips to Make the Binary String Alternating | Medium | Rotations = windows of length n in s + s; count mismatches against 0101… as the window slides | |
| 2461 | Maximum Sum of Distinct Subarrays With Length K | Medium | Fixed window of size k with a count map; valid when it has k distinct values | |
| 2958 | Length of Longest Subarray With at Most K Frequency | Medium | Variable window; only the value just added can break the rule | |
| 3356 | Zero Array Transformation II | Medium | Sweep the array; pull in queries one by one (difference array) until the current index is covered | |
| 1456 | Maximum Number of Vowels in a Substring of Given Length | Medium | Fixed-size sliding window: add the entering character's vowel flag, subtract the leaving one's | |
| 187 | Repeated DNA Sequences | Medium | Slide a 10-letter window; a sequence seen before goes into the answer set | |
| 1151 | Minimum Swaps to Group All 1's Together | Medium | Window size = number of ones; swaps needed = ones missing from the best window | |
| 1438 | Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit | Medium | Sliding window with two monotonic deques tracking the window's max and min | |
| 2271 | Maximum White Tiles Covered by a Carpet | Medium | Start the carpet at each tile range's left end; slide a pointer over fully covered ranges plus one partial | |
| 2653 | Sliding Subarray Beauty | Medium | Values are in [−50, 50]: keep counts per negative value in the window and walk them to find the x-th smallest | |
| 2762 | Continuous Subarrays | Medium | Count windows with max − min ≤ 2: for each right end, every left from the window start works | |
| 3413 | Maximum Coins From K Consecutive Bags | Medium | An optimal window starts at some segment's left end or ends at some segment's right end: slide over segments twice | |
| 2067 | Number of Equal Count Substrings | Medium | For each number of distinct letters u (1..26), slide a window of length u·count and check u letters have exactly count | |
| 1208 | Get Equal Substrings Within Budget | Medium | Sliding window over the costs |s[i] − t[i]|, shrinking while the cost exceeds maxCost | |
| 76 | Minimum Window Substring | Hard | need-counts + missing counter | |
| 239 | Sliding Window Maximum | Hard | Monotonic deque | |
| 992 | Subarrays with K Different Integers | Hard | exactly(k) = atMost(k) − atMost(k−1) | |
| 30 | Substring with Concatenation of All Words | Hard | Window that moves one word at a time, once per starting offset | |
| 1703 | Minimum Adjacent Swaps for K Consecutive Ones | Hard | Shift positions by their rank (p_i − i), then slide a window of k ones and gather around the median using prefix sums | |
| 2009 | Minimum Number of Operations to Make Array Continuous | Hard | Sort the distinct values; slide a value window of width n − 1; keep as many values as fit, replace the rest | |
| 220 | Contains Duplicate III | Hard | Sliding window of the last indexDiff values, bucketed by width valueDiff + 1 |
Worked solutions for the practice list
Problems already solved in section 7 link back up there. Every other problem from the table is solved here.
Easy 643. Maximum Average Subarray I Start here Google Meta Amazon Microsoft
Problem: Find the largest average of any contiguous subarray of length exactly k.
Solve it on LeetCode ↗ · Video walkthrough ↗
nums = [1,12,-5,-6,50,3], k = 412.75000nums = [5], k = 15.00000Constraints: n == nums.length · 1 ≤ k ≤ n ≤ 10⁵ · -10⁴ ≤ nums[i] ≤ 10⁴
Hint 1
Every window has the same size k.
Hint 2
Moving the window one step: add the entering element, subtract the leaving one.
From brute force to the pattern: Summing each window from scratch is O(n · k). Updating the sum incrementally makes each slide O(1): O(n).
Spot it: "Length exactly k" → fixed-size window.
Approach:
- Sum the first
knumbers. - Slide: add
nums[right], subtractnums[right − k]. - Track the maximum window sum.
- Return
max / k(one division at the end).
from typing import List
class Solution:
def findMaxAverage(self, nums: List[int], k: int) -> float:
# Sum of the first window nums[0..k-1]
window_sum = 0
for i in range(k):
window_sum = window_sum + nums[i]
best_sum = window_sum
# Slide: nums[right] comes in, nums[right - k] goes out
for right in range(k, len(nums)):
window_sum = window_sum + nums[right] - nums[right - k]
if window_sum > best_sum:
best_sum = window_sum
# All windows have the same size k, so the biggest sum
# is also the biggest average: divide only once at the end
return best_sum / k
What the code is doing, line by line:
- The first loop adds up
nums[0]…nums[k-1]intowindow_sum; that's the first window, and also the best so far. for right in range(k, len(nums)):nums[right]slides in,nums[right - k]slides out. One addition and one subtraction update the sum.if window_sum > best_sum: remember the largest window sum.return best_sum / k: every window has the same size, so the largest sum is also the largest average. We divide only once, at the end.
Dry run on the example: nums = [1, 12, -5, -6, 50, 3], k = 4 → returns 12.75 (= 51 / 4)
| Step | Window | in | out | window_sum | best_sum |
|---|---|---|---|---|---|
| first window | [1, 12, -5, -6] | – | – | 2 | 2 |
| right = 4 | [12, -5, -6, 50] | 50 | 1 | 51 | 51 |
| right = 5 | [-5, -6, 50, 3] | 3 | 12 | 42 | 51 |
Edge cases to test:
k = n→ the average of everything.- All negative numbers (start the best at the first window, not 0).
k = 1→ the maximum element.
Complexity: O(n) time, O(1) space.
Medium 1004. Max Consecutive Ones III Google Meta Amazon Microsoft
Problem: A binary array; you may flip at most k zeros into ones. What's the longest run of ones you can get?
Solve it on LeetCode ↗ · Video walkthrough ↗
nums = [1,1,1,0,0,0,1,1,1,1,0], k = 26nums = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], k = 310Constraints: 1 ≤ nums.length ≤ 10⁵ · nums[i] is either 0 or 1. · 0 ≤ k ≤ nums.length
Hint 1
Flipping at most k zeros = finding the longest window that contains at most k zeros.
Hint 2
Grow the window; when it holds more than k zeros, shrink from the left until it doesn't.
From brute force to the pattern: Trying every subarray and counting its zeros is O(n²). A window that tracks its zero count moves each edge forward only, giving O(n).
Spot it: Longest contiguous + "at most k" → variable window whose rule is "at most k zeros inside".
Approach:
- Count the zeros inside the window.
- Expand
right; ifnums[right]is 0, add 1 tozeros_in_window. - While
zeros_in_window > k, shrink from the left (subtract 1 when a 0 leaves). - Record the window length.
from typing import List
class Solution:
def longestOnes(self, nums: List[int], k: int) -> int:
left = 0 # left edge of the window
zeros_in_window = 0 # zeros we would have to flip
best_length = 0
for right in range(len(nums)):
# Bring nums[right] in; count it if it is a zero
if nums[right] == 0:
zeros_in_window = zeros_in_window + 1
# More than k zeros: we cannot flip them all, so shrink from the left
while zeros_in_window > k:
if nums[left] == 0:
zeros_in_window = zeros_in_window - 1
left = left + 1
# The window has at most k zeros: all of it can become ones
window_length = right - left + 1
if window_length > best_length:
best_length = window_length
return best_length
What the code is doing, line by line:
zeros_in_windowcounts the zeros insidenums[left..right]; those are the ones we would have to flip.- For each
right: if the new number is 0, add 1 tozeros_in_window. while zeros_in_window > k: we can't flip that many. Moveleftforward; whenever the number leaving is a 0, subtract 1 fromzeros_in_window.- After the loop the window has at most
kzeros, so all of it can become ones: compareright - left + 1withbest_length.
Dry run on the example: nums = [1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0], k = 2 → returns 6
| right | nums[right] | shrink | zeros_in_window | left | window length | best_length |
|---|---|---|---|---|---|---|
| 0 | 1 | – | 0 | 0 | 1 | 1 |
| 1 | 1 | – | 0 | 0 | 2 | 2 |
| 2 | 1 | – | 0 | 0 | 3 | 3 |
| 3 | 0 | – | 1 | 0 | 4 | 4 |
| 4 | 0 | – | 2 | 0 | 5 | 5 |
| 5 | 0 | 4 step(s) | 2 | 4 | 2 | 5 |
| 6 | 1 | – | 2 | 4 | 3 | 5 |
| 7 | 1 | – | 2 | 4 | 4 | 5 |
| 8 | 1 | – | 2 | 4 | 5 | 5 |
| 9 | 1 | – | 2 | 4 | 6 | 6 |
| 10 | 0 | 1 step(s) | 2 | 5 | 6 | 6 |
Edge cases to test:
k = 0→ the longest run of 1s.k ≥number of zeros →n.- All zeros.
Complexity: O(n) time, O(1) space.
Medium 567. Permutation in String Google Meta Amazon Cisco Microsoft Apple
Problem: Does s2 contain a substring that's a rearrangement (permutation) of s1?
Solve it on LeetCode ↗ · Video walkthrough ↗
s1 = "ab", s2 = "eidbaooo"trues1 = "ab", s2 = "eidboaoo"falseConstraints: 1 ≤ s1.length, s2.length ≤ 10⁴ · s1 and s2 consist of lowercase English letters.
Hint 1
s2 contains a permutation of s1 exactly when some window of length len(s1) has the same letter counts.
Hint 2
Slide a fixed window over s2 and update the counts incrementally.
From brute force to the pattern: Generating all permutations of s1 is factorial; sorting every window is O(n · m log m). A fixed-size window with 26 counts checks each position in O(1) (or O(26)): O(n).
Spot it: A permutation of s1 has exactly len(s1) letters → fixed window comparing letter counts.
Approach:
- Count the letters of
s1intoneed[26]. - Slide a window of size
len(s1)overs2, keepinghave[26]up to date (one in, one out). - After each step, compare
have == need. - Return
Trueon the first match, otherwiseFalse.
class Solution:
def checkInclusion(self, s1: str, s2: str) -> bool:
window_size = len(s1) # a permutation of s1 has exactly len(s1) letters
if window_size > len(s2):
return False
# Two lists of 26 counters, one slot per letter: slot 0 = 'a', slot 25 = 'z'
need = [0] * 26
have = [0] * 26
for ch in s1:
letter_index = ord(ch) - ord("a")
need[letter_index] = need[letter_index] + 1
for right in range(len(s2)):
# One letter slides IN on the right
entering_index = ord(s2[right]) - ord("a")
have[entering_index] = have[entering_index] + 1
# Keep the window at most window_size long: one letter slides OUT
if right >= window_size:
leaving_index = ord(s2[right - window_size]) - ord("a")
have[leaving_index] = have[leaving_index] - 1
# Comparing two lists of 26 numbers is a constant amount of work
if have == need:
return True
return False
What the code is doing, line by line:
window_size = len(s1); ifs1is longer thans2, no permutation can fit, so returnFalse.needandhaveare lists of 26 counters, one per letter.ord(ch) - ord("a")turns a letter into its slot number:'a'→ 0,'b'→ 1, …'z'→ 25.- The first loop counts the letters of
s1intoneed. - For each
rightins2: the letter atrightslides in (its slot goes up by 1). if right >= window_size: the letter atright - window_sizeslides out (its slot goes down by 1), so the window never has more thanwindow_sizeletters.if have == need: Python compares the two lists slot by slot. Equal means the window is a permutation ofs1, so returnTrueimmediately. If the loop finishes, returnFalse.
Dry run on the example: s1 = "ab", s2 = "eidbaooo", so need holds a: 1, b: 1 → returns True
| right | entering | leaving | window | non-zero slots of have | have == need? |
|---|---|---|---|---|---|
| 0 | 'e' | – | 'e' | {'e': 1} | no |
| 1 | 'i' | – | 'ei' | {'e': 1, 'i': 1} | no |
| 2 | 'd' | 'e' | 'id' | {'d': 1, 'i': 1} | no |
| 3 | 'b' | 'i' | 'db' | {'b': 1, 'd': 1} | no |
| 4 | 'a' | 'd' | 'ba' | {'a': 1, 'b': 1} | yes → return True |
Edge cases to test:
s1longer thans2→ False.- The match at the very end of
s2. - Repeated letters.
Complexity: O(26·n) = O(n) time, O(1) space.
Medium 904. Fruit Into Baskets Google Meta Amazon Netflix
Problem: Walk along a row of trees, taking one fruit per tree. You have two baskets, each holding one fruit type. The most fruit you can collect = the longest subarray with at most 2 distinct values.
fruits = [1,2,1]3fruits = [0,1,2,2]3Constraints: 1 ≤ fruits.length ≤ 10⁵ · 0 ≤ fruits[i] < fruits.length
Hint 1
You may only hold two fruit types: find the longest subarray with at most 2 distinct values.
Hint 2
Track counts per type in the window; when a third type appears, shrink from the left until one type's count hits 0.
From brute force to the pattern: Every subarray with a distinct-count check is O(n²). A window with a count map of at most three keys moves each edge forward only: O(n).
Spot it: Longest contiguous + "at most 2 distinct" → variable window with a count map.
Approach:
basketmaps fruit type → how many are in the window.- Expand
rightand add its fruit. - While there are more than 2 types, remove
fruits[left](delete the key when it hits 0) and moveleft. - Record the window length.
from typing import List
class Solution:
def totalFruit(self, fruits: List[int]) -> int:
basket = {} # fruit type -> how many of it are in the window
left = 0 # left edge of the window
best_length = 0
for right in range(len(fruits)):
# Pick the fruit at position right
fruit = fruits[right]
if fruit not in basket:
basket[fruit] = 0
basket[fruit] = basket[fruit] + 1
# Three types but only two baskets: give back fruit from the left
while len(basket) > 2:
left_fruit = fruits[left]
basket[left_fruit] = basket[left_fruit] - 1
if basket[left_fruit] == 0:
del basket[left_fruit] # that type has fully left the window
left = left + 1
# At most 2 types now: record how many fruits we hold
window_length = right - left + 1
if window_length > best_length:
best_length = window_length
return best_length
What the code is doing, line by line:
basketmaps fruit type → how many fruits of that type are in the window. Its number of keys,len(basket), is how many types we're carrying.- For each
right: addfruits[right]to the basket (create the key with 0 first if it's a new type). while len(basket) > 2: three types but only two baskets. Give back the fruit atleft(count minus 1), delete the type entirely when its count reaches 0, and moveleftforward.- After shrinking we hold at most 2 types, so
right - left + 1fruits is a valid amount; keep the largest inbest_length.
Dry run on the example: fruits = [1, 2, 3, 2, 2] → returns 4
| right | fruit | shrink | basket after | left | window | best_length |
|---|---|---|---|---|---|---|
| 0 | 1 | – | {1: 1} | 0 | [1] | 1 |
| 1 | 2 | – | {1: 1, 2: 1} | 0 | [1, 2] | 2 |
| 2 | 3 | give back 1 | {2: 1, 3: 1} | 1 | [2, 3] | 2 |
| 3 | 2 | – | {2: 2, 3: 1} | 1 | [2, 3, 2] | 3 |
| 4 | 2 | – | {2: 3, 3: 1} | 1 | [2, 3, 2, 2] | 4 |
Edge cases to test:
- One fruit type only →
n. - Alternating types, e.g.
[1, 2, 1, 2]→ 4. - Fewer than 3 elements.
Complexity: O(n) time, O(1) space (at most 3 keys).
Medium 1658. Minimum Operations to Reduce X to Zero Google Amazon
Problem: Each operation removes the leftmost or rightmost number and subtracts it from x. What's the minimum number of operations to make x exactly 0 (or −1 if impossible)?
nums = [1,1,4,2,3], x = 52nums = [5,6,7,8,9], x = 4-1Constraints: 1 ≤ nums.length ≤ 10⁵ · 1 ≤ nums[i] ≤ 10⁴ · 1 ≤ x ≤ 10⁹
Hint 1
Removing from both ends leaves a contiguous middle. What must the middle's sum be?
Hint 2
Find the longest subarray with sum total − x using a sliding window (all values are positive).
From brute force to the pattern: Trying every split between the left and right removals is O(n²). Inverting the problem (maximise the kept middle with sum total − x) makes it a positive-number sliding window: O(n).
Spot it: Removing from both ends is awkward. Flip it: what remains is a contiguous middle that sums to total − x. Fewest removals = longest such middle. All values are positive → sliding window.
Approach:
goal = sum(nums) − x. Ifgoal < 0return −1; ifgoal == 0returnn.- Expand
right; while the window sum exceedsgoal, shrink from the left. - If the window sum equals
goal, record its length. - Answer =
n − longest, or −1 if no window was found.
from typing import List
class Solution:
def minOperations(self, nums: List[int], x: int) -> int:
# Add up all the numbers
total = 0
for i in range(len(nums)):
total = total + nums[i]
# The untouched middle part must add up to goal
goal = total - x
if goal < 0:
return -1 # even removing everything is not enough
if goal == 0:
return len(nums) # we must remove every number
left = 0 # left edge of the middle window
window_sum = 0 # sum of nums[left..right]
longest = -1 # longest middle with sum == goal (-1 = none yet)
for right in range(len(nums)):
window_sum = window_sum + nums[right]
# All numbers are positive, so removing from the left lowers the sum
while window_sum > goal:
window_sum = window_sum - nums[left]
left = left + 1
if window_sum == goal:
window_length = right - left + 1
if window_length > longest:
longest = window_length
# Fewest removals = everything except the longest middle
if longest == -1:
return -1
else:
return len(nums) - longest
What the code is doing, line by line:
- The first loop adds up every number into
total. - Removing numbers from the two ends always leaves a contiguous middle part. The removed numbers must add up to
x, so the middle must add up togoal = total - x. goal < 0: even removing everything isn't enough, return -1.goal == 0: we must remove every number, returnlen(nums).- Now find the longest middle window with sum exactly
goal. For eachright, addnums[right];while window_sum > goal, subtractnums[left]and moveleft(all numbers are positive, so this lowers the sum). if window_sum == goal: this middle works; keep its length inlongestif it's the biggest so far.- At the end: if
longestis still -1 nothing worked, return -1. Otherwise the fewest operations is everything outside the middle:len(nums) - longest.
Dry run on the example: nums = [3, 2, 20, 1, 1, 3], x = 10: total = 30, goal = 20 → returns 5 (= 6 − 1)
| right | nums[right] | shrink | window (middle) | window_sum | == goal? | longest |
|---|---|---|---|---|---|---|
| 0 | 3 | – | [3] | 3 | no | -1 |
| 1 | 2 | – | [3, 2] | 5 | no | -1 |
| 2 | 20 | drop 3, 2 | [20] | 20 | yes | 1 |
| 3 | 1 | drop 20 | [1] | 1 | no | 1 |
| 4 | 1 | – | [1, 1] | 2 | no | 1 |
| 5 | 3 | – | [1, 1, 3] | 5 | no | 1 |
Edge cases to test:
x == total→ remove everything (n).x > total→ −1.- No valid middle exists → −1.
Complexity: O(n) time, O(1) space.
Hard 992. Subarrays with K Different Integers Google Amazon Microsoft
Problem: Count the subarrays that contain exactly k distinct integers.
nums = [1,2,1,2,3], k = 27nums = [1,2,1,3,4], k = 33Constraints: 1 ≤ nums.length ≤ 2 * 10⁴ · 1 ≤ nums[i], k ≤ nums.length
Hint 1
"Exactly k distinct" is hard to window directly, but "at most k distinct" is easy.
Hint 2
exactly(k) = atMost(k) − atMost(k − 1). In atMost, each right edge adds right − left + 1 subarrays.
From brute force to the pattern: Counting distinct values in every subarray is O(n²). The at-most trick turns it into two linear sliding-window passes: O(n).
Spot it: "Exactly k" is not monotonic (growing a window can make it valid, then invalid), but "at most k" is. Use exactly(k) = atMost(k) − atMost(k − 1).
Key insight: In an at-most window [left, right], every subarray that ends at right and starts anywhere in [left, right] is also valid: that's right − left + 1 subarrays.
Approach:
- Write a helper
at_most(nums, max_distinct): a variable window that keeps ≤max_distinctdistinct values (a dict of counts; shrink while it has too many keys). - After shrinking for each
right, addright − left + 1to the total. - Return
at_most(nums, k) − at_most(nums, k − 1).
from typing import List
class Solution:
def subarraysWithKDistinct(self, nums: List[int], k: int) -> int:
# "exactly k" = "at most k" minus "at most k - 1"
at_most_k = self.at_most(nums, k)
at_most_k_minus_1 = self.at_most(nums, k - 1)
return at_most_k - at_most_k_minus_1
def at_most(self, nums: List[int], max_distinct: int) -> int:
# Counts subarrays that contain at most max_distinct different values
counts = {} # value -> how many times it is in the window
left = 0
total = 0
for right in range(len(nums)):
value = nums[right]
if value not in counts:
counts[value] = 0
counts[value] = counts[value] + 1
# Too many different values: shrink from the left
while len(counts) > max_distinct:
left_value = nums[left]
counts[left_value] = counts[left_value] - 1
if counts[left_value] == 0:
del counts[left_value]
left = left + 1
# Every subarray that ends at right and starts anywhere in
# [left, right] is valid: that is right - left + 1 subarrays
total = total + (right - left + 1)
return total
What the code is doing, line by line:
subarraysWithKDistinctcalls the helper twice: subarrays with at mostkdistinct values, minus those with at mostk - 1. What's left is exactlyk.at_most(nums, max_distinct)is a normal longest-valid window.countsmaps value → how many times it's in the window, solen(counts)is the number of distinct values.- For each
right: addnums[right]tocounts. while len(counts) > max_distinct: too many distinct values. Removenums[left](delete the key when its count reaches 0) and moveleft.total = total + (right - left + 1): every subarray that ends atrightand starts anywhere fromlefttorightis valid. There areright - left + 1of them, and each is counted exactly once (at its end index).- When
max_distinctis 0, the window is always shrunk to empty and adds 0, soat_most(nums, 0)correctly returns 0.
Dry run on the example: nums = [1, 2, 1, 2, 3], k = 2: at_most(2) = 12, at_most(1) = 5, so it returns 12 − 5 = 7
| right | value | at_most(2) window | + added | at_most(2) total | at_most(1) window | + added | at_most(1) total |
|---|---|---|---|---|---|---|---|
| 0 | 1 | [1] | 1 | 1 | [1] | 1 | 1 |
| 1 | 2 | [1, 2] | 2 | 3 | [2] | 1 | 2 |
| 2 | 1 | [1, 2, 1] | 3 | 6 | [1] | 1 | 3 |
| 3 | 2 | [1, 2, 1, 2] | 4 | 10 | [2] | 1 | 4 |
| 4 | 3 | [2, 3] | 2 | 12 | [3] | 1 | 5 |
Edge cases to test:
k = 1→ count runs of equal values.klarger than the number of distinct values → 0.- All elements the same.
Complexity: O(n) time, O(n) space for the counts.
Easy 121. Best Time to Buy and Sell Stock Start here Google Meta Nvidia Amazon Cisco Microsoft Apple
Problem: prices[i] is a stock's price on day i. Choose one day to buy and a later day to sell. Return the maximum profit, or 0 if no profit is possible.
Solve it on LeetCode ↗ · Video walkthrough ↗
prices = [7,1,5,3,6,4]5prices = [7,6,4,3,1]0Constraints: 1 ≤ prices.length ≤ 10⁵ · 0 ≤ prices[i] ≤ 10⁴
Hint 1
For each possible sell day, which buy day is best?
Hint 2
Keep the cheapest price seen so far; today's profit is today's price minus that minimum.
From brute force to the pattern: Trying every buy day with every later sell day is O(n²). The best buy for any sell day is simply the minimum so far, which a single variable tracks: O(n).
Spot it: A pair (buy, sell) with buy < sell that maximises prices[sell] − prices[buy]. Think of it as a window: sell scans forward, and buy stays on the cheapest day seen so far.
Key insight: If today's price is lower than the current buy price, any future sale is better from today, so the left edge of the window jumps here. Otherwise, today is a candidate sell day.
Approach:
buy = 0,best = 0.- For each later day
sell: ifprices[sell] < prices[buy], movebuytosell(a new, cheaper starting point). - Otherwise compute the profit
prices[sell] − prices[buy]and keep the maximum.
class Solution:
def maxProfit(self, prices: List[int]) -> int:
buy = 0 # day of the cheapest price seen so far (left edge of the window)
best = 0 # no transaction at all gives 0 profit
for sell in range(1, len(prices)):
if prices[sell] < prices[buy]:
# a cheaper day: every later sale is better from here
buy = sell
else:
profit = prices[sell] - prices[buy]
best = max(best, profit)
return best
What the code is doing, line by line:
buy = 0: the left edge of the window, always the cheapest day so far.best = 0: if prices only fall, we simply don't trade.for sell in range(1, len(prices)):the right edge walks forward one day at a time.if prices[sell] < prices[buy]: buy = sell: today is cheaper than our buy day, so restart the window here.else:today could be a sell day: compute the profit and keep the best.
Dry run on the example:
prices = [7, 1, 5, 3, 6, 4] (LeetCode Example 1). Returns 5 (buy at 1, sell at 6).
| sell | prices[sell] | prices[buy] | action | best |
|---|---|---|---|---|
| 1 | 1 | 7 | cheaper → buy = 1 | 0 |
| 2 | 5 | 1 | profit 4 | 4 |
| 3 | 3 | 1 | profit 2 | 4 |
| 4 | 6 | 1 | profit 5 | 5 |
| 5 | 4 | 1 | profit 3 | 5 |
Edge cases to test:
- Prices only fall → 0.
- A single day → 0.
- The minimum comes after the maximum, e.g.
[3, 5, 1]→ 2 (not 4).
Complexity: O(n) time, O(1) space.
Easy 219. Contains Duplicate II Google Meta Amazon Microsoft Netflix
Problem: Return True if there are two indices i ≠ j with nums[i] == nums[j] and |i − j| ≤ k.
Solve it on LeetCode ↗ · Video walkthrough ↗
nums = [1,2,3,1], k = 3truenums = [1,0,1,1], k = 1trueConstraints: 1 ≤ nums.length ≤ 10⁵ · -10⁹ ≤ nums[i] ≤ 10⁹ · 0 ≤ k ≤ 10⁵
Hint 1
"Within distance k" means inside a window of the last k positions.
Hint 2
Keep those values in a set; when the window grows past k, remove the one that fell out.
From brute force to the pattern: Comparing every pair within distance k is O(n · k). A set holding just the current window answers "seen recently?" in O(1): O(n).
Spot it: "Within distance k" = within a window of the last k elements. Keep those values in a set: a value already in the set is a close duplicate.
Approach:
- Walk the array. The set
windowholds the values of the previouskpositions. - If
nums[i]is already inwindow, a duplicate within distancekexists: returnTrue. - Add
nums[i]. If the window now holds more thankvalues, remove the one that just fell out,nums[i − k]. - If the loop ends, no close duplicate exists.
class Solution:
def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
# the values of the last k positions
window = set()
for i in range(len(nums)):
if nums[i] in window:
# the same value appeared at most k positions ago
return True
window.add(nums[i])
if len(window) > k:
# nums[i - k] is now too far away to pair with anything ahead
window.remove(nums[i - k])
return False
What the code is doing, line by line:
window = set(): at mostkvalues, the ones close enough to pair with the current position.if nums[i] in window: return True: anO(1)set lookup finds a duplicate within distancek.window.add(nums[i]): the current value joins the window for the positions that follow.if len(window) > k: window.remove(nums[i - k]): keep the window atkvalues. The window never holds a duplicate (we would have returned), so its size equals the number of positions it covers.return False: no pair was close enough.
Dry run on the example:
nums = [1, 2, 3, 1, 2, 3], k = 2 (LeetCode Example 3). Returns False: equal values are always 3 apart.
| i | nums[i] | in window? | window after (size ≤ k) |
|---|---|---|---|
| 0 | 1 | no | {1} |
| 1 | 2 | no | {1, 2} |
| 2 | 3 | no | add 3, too big → remove nums[0] = 1 → {2, 3} |
| 3 | 1 | no | add 1 → remove nums[1] = 2 → {3, 1} |
| 4 | 2 | no | add 2 → remove nums[2] = 3 → {1, 2} |
| 5 | 3 | no | add 3 → remove nums[3] = 1 → {2, 3} |
The loop ends without a hit, so the answer is False. With nums = [1, 2, 3, 1], k = 3 the second 1 would find the first one still in the window.
Edge cases to test:
k = 0→ always False.- The duplicate exactly
kapart → True. - Duplicates only further apart than
k→ False.
Complexity: O(n) time, O(k) space.
Hard 30. Substring with Concatenation of All Words Google Amazon Microsoft
Problem: words are all the same length. Return every starting index in s of a substring that is some concatenation of all the words, each used exactly as many times as it appears in words (any order).
Solve it on LeetCode ↗ · Video walkthrough ↗
s = "barfoothefoobarman", words = ["foo","bar"][0,9]s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"][]Constraints: 1 ≤ s.length ≤ 10⁴ · 1 ≤ words.length ≤ 5000 · 1 ≤ words[i].length ≤ 30 · s and words[i] consist of lowercase English letters.
Hint 1
All words have the same length L. Read s in chunks of L characters.
Hint 2
Run a sliding window whose unit is one word, once for each starting offset 0..L − 1, with a count map of the words inside.
From brute force to the pattern: Checking every start index by slicing len(words) words and comparing counts is O(n · m · L). A word-sized sliding window per offset reuses the counts as it moves, so each word slice is handled a constant number of times: O(n · L).
Spot it: "Contiguous substring" + "contains exactly these counts" → a variable sliding window with a count map, like Minimum Window Substring. The twist: the window's unit is a whole word of length L, so it moves in jumps of L.
Key insight: A match can start at any index, but jumping by L only visits indices with the same remainder mod L. So run the word-sized window L times, once for each offset 0 .. L − 1. Each run is linear.
Approach:
- Count how many times each word is needed (
need). - For each offset
0 .. L − 1: start an empty window atleft = offsetand read words atright = offset, offset + L, …. - If the word is not in
need, no answer can include it. Clear the window and restart just after it. - Otherwise add it to
seen. If that word now appears too often, drop words from the left until it doesn't. - If the window holds exactly
len(words)words, recordleft, then drop one word from the left to keep searching.
class Solution:
def findSubstring(self, s: str, words: List[str]) -> List[int]:
word_len = len(words[0])
total_words = len(words)
# how many copies of each word a valid window must contain
need = {}
for w in words:
need[w] = need.get(w, 0) + 1
result = []
# the window jumps a whole word at a time, so try every starting offset
for offset in range(word_len):
left = offset
seen = {} # word counts inside the current window
count = 0 # number of words in the current window
for right in range(offset, len(s) - word_len + 1, word_len):
word = s[right:right + word_len]
if word not in need:
# this word can never be part of an answer: restart after it
seen = {}
count = 0
left = right + word_len
continue
seen[word] = seen.get(word, 0) + 1
count = count + 1
# too many copies of this word: shrink from the left until it fits again
while seen[word] > need[word]:
left_word = s[left:left + word_len]
seen[left_word] = seen[left_word] - 1
count = count - 1
left = left + word_len
if count == total_words:
# every word is present the right number of times
result.append(left)
# drop the leftmost word and keep sliding
left_word = s[left:left + word_len]
seen[left_word] = seen[left_word] - 1
count = count - 1
left = left + word_len
return result
What the code is doing, line by line:
need: a count map of the words, e.g.{"foo": 1, "bar": 1}. Words can repeat, so this must be a count, not a set.for offset in range(word_len):answers can start at any index, but one pass of jumps only reaches indices with the same remainder.word_lenpasses cover them all.for right in range(offset, len(s) - word_len + 1, word_len):rightis the start of the next word to bring into the window.if word not in need:a foreign word poisons every window containing it, so reset and start the window after it.seen[word] = seen.get(word, 0) + 1,count = count + 1: grow the window by one word.while seen[word] > need[word]:the new word appears too often; remove words from the left (updatingseenandcount) until it fits.if count == total_words:the window is a full concatenation: record its start, then drop its first word so the search continues.
Dry run on the example:
s = "barfoothefoobarman", words = ["foo", "bar"] (LeetCode Example 1). word_len = 3. Returns [0, 9]. Offset 0 finds both answers; offsets 1 and 2 find nothing.
| right | word | what happens | window after | result |
|---|---|---|---|---|
| 0 | "bar" | add | bar (left = 0) | [] |
| 3 | "foo" | add → 2 words = all words → record 0, drop bar | foo (left = 3) | [0] |
| 6 | "the" | not a needed word → reset | empty (left = 9) | [0] |
| 9 | "foo" | add | foo (left = 9) | [0] |
| 12 | "bar" | add → 2 words → record 9, drop foo | bar (left = 12) | [0, 9] |
| 15 | "man" | not a needed word → reset | empty (left = 18) | [0, 9] |
Edge cases to test:
- Repeated words in
words, e.g.["foo", "foo"]. - A foreign word in the middle resets the window.
sshorter than all the words together →[].
Complexity: O(n · L) time: L offsets, each scanning about n / L words, and slicing a word costs O(L). O(m) space for the two maps, where m is the number of words.
Medium 395. Longest Substring with At Least K Repeating Characters Google Amazon
Problem: Return the length of the longest substring in which every character appears at least k times.
s = "aaabb", k = 33s = "ababbc", k = 25Constraints: 1 ≤ s.length ≤ 10⁴ · s consists of only lowercase English letters. · 1 ≤ k ≤ 10⁵
Hint 1
Why doesn't a normal sliding window work here?
Hint 2
Fix the number of distinct letters t in the window (1 to 26) and run a window for each t.
From brute force to the pattern: Checking every substring is O(n²). Fixing the number of distinct letters makes the window monotonic, giving 26 linear passes.
Spot it: A plain window fails: adding a character can make the window invalid or valid, so it's not monotonic. Fixing the number of distinct letters t restores monotonicity: for each t in 1..26, keep a window with at most t distinct letters and check whether all of them reach k.
Why not another pattern? Divide and conquer also works: any character with total count < k can't be in the answer, so split the string around it and recurse (O(26 · n) too). Checking every substring is O(n²).
Approach:
- For each
targetfrom 1 to 26 (number of distinct letters allowed): - Slide a window: add
s[right]; while the window has more thantargetdistinct letters, shrink from the left. - Track
distinctandat_least_k(letters whose count reachedk). If both equaltarget, the window is valid. - Keep the longest valid window over all targets.
class Solution:
def longestSubstring(self, s: str, k: int) -> int:
best = 0
for target in range(1, 27): # exactly `target` distinct letters in the window
count = [0] * 26
distinct = 0 # letters present in the window
at_least_k = 0 # letters present at least k times
left = 0
for right in range(len(s)):
c = ord(s[right]) - ord("a")
if count[c] == 0:
distinct = distinct + 1
count[c] = count[c] + 1
if count[c] == k:
at_least_k = at_least_k + 1
while distinct > target: # too many letters: shrink
d = ord(s[left]) - ord("a")
if count[d] == k:
at_least_k = at_least_k - 1
count[d] = count[d] - 1
if count[d] == 0:
distinct = distinct - 1
left = left + 1
if distinct == target and at_least_k == target:
best = max(best, right - left + 1) # every letter appears >= k times
return best
What the code is doing, line by line:
- The outer loop fixes how many different letters the window may contain; with that fixed, "too many letters" is a monotonic condition again.
distinctandat_least_kare updated exactly when a count crosses 0 ork, so both stayO(1)per step.- The inner
whilerestores "at mosttargetdistinct letters" by shrinking from the left. - A window is valid when it has exactly
targetletters and all of them reachedk.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
k = 1→ the whole string.klarger than the string → 0.- A letter that appears fewer than
ktimes splits the string.
Complexity: O(26 · n) time, O(1) space.
Medium 413. Arithmetic Slices Google Amazon
Problem: Count the contiguous subarrays of length ≥ 3 whose consecutive differences are all equal.
nums = [1,2,3,4]3nums = [1]0Constraints: 1 ≤ nums.length ≤ 5000 · -1000 ≤ nums[i] ≤ 1000
Hint 1
Look at runs of equal differences. How many slices does a run of length L contain?
Hint 2
Walk the array; each time the run grows, the number of slices ending at the new element goes up by one.
From brute force to the pattern: Testing every subarray is O(n²). A run of length L contains (L−1)(L−2)/2 slices, and counting them incrementally as the run grows takes one pass.
Spot it: Arithmetic slices live inside maximal runs with a constant difference. Extending a run by one element creates new slices that end at that element: a window that grows while the difference stays the same.
Why not another pattern? Checking every subarray is O(n²) (or O(n³) naively). This is also a tiny DP: dp[i] = dp[i − 1] + 1 when the difference repeats.
Approach:
current= number of arithmetic slices ending at the current index.- If
nums[i] − nums[i−1] == nums[i−1] − nums[i−2], every slice ending ati − 1extends, plus one new length-3 slice:current += 1. - Otherwise the run breaks:
current = 0. Addcurrentto the total each step.
class Solution:
def numberOfArithmeticSlices(self, nums: List[int]) -> int:
total = 0
current = 0 # arithmetic slices ending at index i
for i in range(2, len(nums)):
if nums[i] - nums[i - 1] == nums[i - 1] - nums[i - 2]:
current = current + 1 # the run grows: one more slice ends here than before
else:
current = 0 # the difference changed: the run is broken
total = total + current
return total
What the code is doing, line by line:
- Start at index 2: a slice needs three elements.
- Same difference as before: all slices ending at
i − 1extend toi, and[i−2, i−1, i]is new, hence+ 1. - A different difference resets the run.
totalaccumulates the slices ending at each index.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Fewer than 3 elements → 0.
- One long run, e.g.
[1, 2, 3, 4]→ 3. - Constant arrays (difference 0) count too.
Complexity: O(n) time, O(1) space.
Medium 713. Subarray Product Less Than K Google Meta Amazon Apple
Problem: Count the contiguous subarrays whose product is strictly less than k (all numbers are positive).
nums = [10,5,2,6], k = 1008nums = [1,2,3], k = 00Constraints: 1 ≤ nums.length ≤ 3 * 10⁴ · 1 ≤ nums[i] ≤ 1000 · 0 ≤ k ≤ 10⁶
Hint 1
With positive numbers, a window's product grows as it expands and shrinks as it contracts.
Hint 2
For each right edge, shrink the window until valid; then all subarrays ending there and starting inside it count.
From brute force to the pattern: Multiplying out every subarray is O(n²). The window stays valid as it slides, and counting right − left + 1 per step counts all valid subarrays in one pass.
Spot it: Positive numbers mean the product only grows when the window grows and only shrinks when it shrinks: a variable sliding window. When the window [left, right] is valid, every subarray ending at right inside it is valid too.
Why not another pattern? Checking all subarrays is O(n²). Logarithms plus prefix sums and binary search work but add floating-point risk and log n.
Approach:
- If
k ≤ 1, no product of positive integers is below it: return 0. - Extend
right, multiplyingproductbynums[right]. - While
product ≥ k, divide outnums[left]and moveleftright. - Add
right − left + 1: the number of valid subarrays ending atright.
class Solution:
def numSubarrayProductLessThanK(self, nums: List[int], k: int) -> int:
if k <= 1:
return 0 # products of positive integers are at least 1
product = 1
left = 0
count = 0
for right in range(len(nums)):
product = product * nums[right] # grow the window
while product >= k:
product = product // nums[left] # shrink until valid again
left = left + 1
count = count + (right - left + 1) # subarrays ending at right
return count
What the code is doing, line by line:
k <= 1guard: otherwise the shrinking loop could moveleftpastright.- Multiplying by
nums[right]adds the new element to the window's product. - The
whiledivides out elements from the left until the product is belowkagain (exact integer division becausenums[left]divides the product). right - left + 1: subarrays[left..right],[left+1..right], …,[right..right]all have product <k.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
k = 0ork = 1→ 0.- A single element ≥
k(the window becomes empty). - All ones with
k = 2→n(n + 1)/2.
Complexity: O(n) time (each index enters and leaves the window once), O(1) space.
Medium 718. Maximum Length of Repeated Subarray Google Netflix
Problem: Return the length of the longest subarray that appears in both nums1 and nums2.
nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7]3nums1 = [0,0,0,0,0], nums2 = [0,0,0,0,0]5Constraints: 1 ≤ nums1.length, nums2.length ≤ 1000 · 0 ≤ nums1[i], nums2[i] ≤ 100
Hint 1
A common subarray means a run of equal elements when the arrays are aligned at the right offset.
Hint 2
Try every offset and find the longest streak of matches on it (or use the DP on (i, j)).
From brute force to the pattern: Comparing every pair of start positions and extending is O(m · n · min(m, n)). Each alignment is scanned once with a running streak, so the whole thing is O(m · n).
Spot it: A common subarray is a run of matches on one alignment (offset) of the two arrays. Sliding one array across the other and scanning each alignment for its longest run of equal elements is a window-style scan with O(1) memory.
Why not another pattern? The classic DP dp[i][j] = dp[i−1][j−1] + 1 on matches is also O(m · n) but uses O(m · n) (or O(n)) memory. Binary search on the length with rolling hashes is O((m + n) log) but more complex.
Approach:
- For every offset (how far
nums2is shifted relative tonums1), walk the overlapping positions together. - Keep
run= length of the current streak of equal pairs; reset it on a mismatch. - The best streak over all offsets is the answer.
class Solution:
def findLength(self, nums1: List[int], nums2: List[int]) -> int:
m = len(nums1)
n = len(nums2)
best = 0
for offset in range(-(n - 1), m): # nums2[j] lines up with nums1[j + offset]
run = 0
for j in range(n):
i = j + offset
if i < 0 or i >= m:
continue # outside the overlap
if nums1[i] == nums2[j]:
run = run + 1 # the matching streak continues
best = max(best, run)
else:
run = 0 # a mismatch breaks the streak
return best
What the code is doing, line by line:
offsetcovers every way of lining up the two arrays.- For a fixed offset, a common subarray is a run of consecutive equal pairs.
runcounts the current streak;bestkeeps the longest streak seen on any alignment.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- No common elements → 0.
- Identical arrays → their length.
- The common part at the end of one array and the start of the other.
Complexity: O(m · n) time, O(1) space.
Medium 1343. Number of Sub-arrays of Size K and Average Greater than or Equal to Threshold Google Microsoft
Problem: Count the subarrays of length k whose average is at least threshold.
arr = [2,2,2,2,5,5,5,8], k = 3, threshold = 43arr = [11,13,17,23,29,31,7,5,2,3], k = 3, threshold = 56Constraints: 1 ≤ arr.length ≤ 10⁵ · 1 ≤ arr[i] ≤ 10⁴ · 1 ≤ k ≤ arr.length · 0 ≤ threshold ≤ 10⁴
Hint 1
All windows have size k. How do you get the next window's sum from the previous one?
Hint 2
Compare the sum with k · threshold to avoid division.
From brute force to the pattern: Recomputing each window's sum is O(n · k). Updating it as the window slides is O(1) per step: O(n) total.
Spot it: Every window has the same size k: a fixed sliding window whose sum is updated in O(1) per step.
Why not another pattern? Summing each window from scratch is O(n · k). Prefix sums also give each window sum in O(1), at the cost of an extra array.
Approach:
- Compare sums instead of averages: average ≥ threshold ⇔ sum ≥
k · threshold(no division). - Sum the first
kelements. - Slide: add the entering element, subtract the leaving one; count windows whose sum reaches the target.
class Solution:
def numOfSubarrays(self, arr: List[int], k: int, threshold: int) -> int:
target = k * threshold # compare sums instead of averages
window = sum(arr[:k])
count = 1 if window >= target else 0
for right in range(k, len(arr)):
window = window + arr[right] - arr[right - k] # slide by one
if window >= target:
count = count + 1
return count
What the code is doing, line by line:
target = k * threshold: multiplying both sides bykavoids floating-point averages.window = sum(arr[:k]): the first window.- Each slide adds
arr[right]and removesarr[right - k], the element that just left.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
k = n→ one window.threshold = 0→ every window counts.k = 1→ count the elements ≥ threshold.
Complexity: O(n) time, O(1) space.
Medium 1358. Number of Substrings Containing All Three Characters Google Meta Microsoft
Problem: Count the substrings of s (made of a, b, c) that contain at least one of each letter.
s = "abcabc"10s = "aaacb"3Constraints: 3 ≤ s.length ≤ 5 x 10⁴ · s only consists of 'a', 'b' or 'c' characters.
Hint 1
If s[i..j] contains all three letters, does s[i−1..j]?
Hint 2
For each right edge, shrink the window until it's no longer valid; the number of removed starts is the number of valid substrings.
From brute force to the pattern: Checking every substring for the three letters is O(n²). Validity is monotonic in the start position, so a window counts all valid starts for each end in amortised O(1).
Spot it: "At least one of each" is monotonic: once a window is valid, extending it stays valid. So for each right edge, find the largest left edge that still keeps it valid; every start at or before that edge works.
Why not another pattern? Checking every substring is O(n²). Tracking the last index of each letter is an even shorter equivalent (count += min(last) + 1).
Approach:
- Grow the window to the right, counting letters.
- While the window has all three letters, shrink from the left.
- After shrinking,
leftis the number of valid starts for this right edge (starts0..left−1); add it.
class Solution:
def numberOfSubstrings(self, s: str) -> int:
count = {"a": 0, "b": 0, "c": 0}
left = 0
total = 0
for right in range(len(s)):
count[s[right]] = count[s[right]] + 1
while count["a"] > 0 and count["b"] > 0 and count["c"] > 0:
count[s[left]] = count[s[left]] - 1 # shrink while still containing all three
left = left + 1
total = total + left # starts 0..left-1 all give a valid substring ending here
return total
What the code is doing, line by line:
countholds the letters insides[left..right].- The
whileshrinks until the window is just missing one letter; at that points[left − 1 .. right]was the shortest valid window. - Any start from 0 to
left − 1gives a valid substring ending atright, so addleft.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- A string missing a letter → 0.
"abc"→ 1.- Long runs of one letter.
Complexity: O(n) time, O(1) space.
Medium 1423. Maximum Points You Can Obtain from Cards Google Amazon Microsoft
Problem: Take exactly k cards, each from the left or right end of the row. Maximise the total points.
cardPoints = [1,2,3,4,5,6,1], k = 312cardPoints = [2,2,2], k = 24Constraints: 1 ≤ cardPoints.length ≤ 10⁵ · 1 ≤ cardPoints[i] ≤ 10⁴ · 1 ≤ k ≤ cardPoints.length
Hint 1
After taking k cards from the ends, what shape do the remaining cards have?
Hint 2
They form a contiguous block of n − k. Find the block with the smallest sum.
From brute force to the pattern: Trying every sequence of left/right picks is 2ᵏ. Thinking about the leftover block turns it into a fixed sliding window: O(n).
Spot it: Whatever you take from the ends, the cards left over form one contiguous block of length n − k. Maximising what you take means minimising that block's sum: a fixed-size sliding window.
Why not another pattern? Trying every split (i from the left, k − i from the right) with prefix sums is also O(k) and equally good. Recursion over choices is exponential.
Approach:
window = n − k. Sum the firstwindowcards.- Slide the window across, tracking the minimum window sum.
- Answer = total − minimum window sum.
class Solution:
def maxScore(self, cardPoints: List[int], k: int) -> int:
n = len(cardPoints)
size = n - k # the cards we leave behind form one block of this size
total = sum(cardPoints)
if size == 0:
return total # take everything
window = sum(cardPoints[:size])
smallest = window
for right in range(size, n):
window = window + cardPoints[right] - cardPoints[right - size] # slide the block
smallest = min(smallest, window)
return total - smallest # what's not left behind is what we took
What the code is doing, line by line:
size = n - k: the untouched middle block always has this length.if size == 0: taking all cards is the only option.- The fixed window slides over all blocks of length
size, tracking the smallest sum. total - smallest: the best score.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
k = n→ the total.k = 1→ the larger end card.- All cards equal.
Complexity: O(n) time, O(1) space.
Medium 1461. Check If a String Contains All Binary Codes of Size K Google Microsoft
Problem: Return True if every binary string of length k appears as a substring of s.
s = "00110110", k = 2trues = "0110", k = 1trueConstraints: 1 ≤ s.length ≤ 5 * 10⁵ · s[i] is either '0' or '1'. · 1 ≤ k ≤ 20
Hint 1
Every length-k substring is a window. How many distinct codes are needed?
Hint 2
Encode each window as an integer that you update in O(1) as the window slides.
From brute force to the pattern: Slicing every window costs O(k) each; a rolling integer makes each step O(1), so the scan is O(n).
Spot it: All substrings of length k are the windows of a fixed size. A rolling bit value (shift left, add the new bit, mask to k bits) gives each window's code in O(1); collect them in a set and check whether there are 2ᵏ of them.
Why not another pattern? Slicing each window as a string is O(k) per window. Generating all 2ᵏ codes and searching for each is O(2ᵏ · n).
Approach:
- If there are fewer than
2ᵏwindows, return False early. - Keep
value= the lastkbits as an integer:value = ((value << 1) & mask) | bit. - Once at least
kbits have been read, addvalueto a set. - Return whether the set has
2ᵏcodes.
class Solution:
def hasAllCodes(self, s: str, k: int) -> bool:
need = 1 << k # 2^k different codes
if len(s) - k + 1 < need:
return False # not enough windows to contain every code
mask = need - 1 # keeps only the last k bits
seen = set()
value = 0
for i in range(len(s)):
value = ((value << 1) & mask) | (s[i] == "1") # slide the k-bit window
if i >= k - 1:
seen.add(value) # the window s[i-k+1 .. i]
return len(seen) == need
What the code is doing, line by line:
1 << kis2ᵏ; with fewer windows than that, some code must be missing.(value << 1) & mask: shift in a new bit and drop the oldest one;| (s[i] == "1")adds the new bit (True counts as 1).- From index
k − 1on,valueencodes a full window. - Distinct window codes are collected in
seen.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
klarger thanlen(s)→ False.k = 1: both0and1must appear.- Many repeated windows.
Complexity: O(n) time, O(2ᵏ) space.
Medium 1695. Maximum Erasure Value Google
Problem: Erase one subarray with all unique elements; its score is the sum of its elements. Return the maximum score.
nums = [4,2,4,5,6]17nums = [5,2,1,2,5,2,1,2,5]8Constraints: 1 ≤ nums.length ≤ 10⁵ · 1 ≤ nums[i] ≤ 10⁴
Hint 1
This is "longest substring without repeating characters" with sums.
Hint 2
Keep a set of values in the window and a running sum; shrink from the left when a duplicate arrives.
From brute force to the pattern: Summing every unique subarray is O(n²). A window that only moves forward handles each element twice: O(n).
Spot it: "Contiguous + all unique" is the Longest Substring Without Repeating Characters window, keeping a running sum instead of a length.
Why not another pattern? Checking every subarray is O(n²). Prefix sums alone can't enforce uniqueness. The window works because all values are positive: extending a valid window can only increase its sum, so the best window ending at each position is the longest unique one.
Approach:
- Grow the window to the right, adding to
window_sum. - If the new value is already in the window, remove from the left until it isn't.
- Track the best
window_sum.
class Solution:
def maximumUniqueSubarray(self, nums: List[int]) -> int:
inside = set() # values currently in the window
left = 0
window_sum = 0
best = 0
for x in nums:
while x in inside:
inside.remove(nums[left]) # drop from the left until x is unique
window_sum = window_sum - nums[left]
left = left + 1
inside.add(x)
window_sum = window_sum + x
best = max(best, window_sum)
return best
What the code is doing, line by line:
insideis the set of values in the window, so the uniqueness check isO(1).- The
whileremoves elements from the left until the old copy ofxis gone. - Add
xand update the running sum and the best score.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- All values distinct → the whole sum.
- All values equal → that value.
- A duplicate far back in the window.
Complexity: O(n) time, O(n) space.
Medium 1838. Frequency of the Most Frequent Element Google Amazon Microsoft
Problem: In one operation you can increment any element by 1. With at most k operations, maximise the frequency of some element.
nums = [1,2,4], k = 53nums = [1,4,8,13], k = 52Constraints: 1 ≤ nums.length ≤ 10⁵ · 1 ≤ nums[i] ≤ 10⁵ · 1 ≤ k ≤ 10⁵
Hint 1
Which elements would you raise to match a chosen value? Think about the sorted order.
Hint 2
Use a window over the sorted array whose cost is nums[right] × size − sum.
From brute force to the pattern: For every target value, raising the closest smaller values greedily is O(n²). Sorting makes the cost of a window easy to compute, and the window only moves forward: O(n log n) total.
Spot it: After sorting, it's best to raise a contiguous block of values up to its largest element. The cost of a window [left, right] is nums[right] × size − window_sum, which only grows when the window grows: a variable sliding window.
Why not another pattern? For each target value, binary-searching the farthest start with prefix sums is O(n log n) after sorting; the window is O(n) after sorting.
Approach:
- Sort
nums. - Extend
right, adding towindow_sum. - While raising everything in the window to
nums[right]costs more thank, removenums[left]. - Track the longest window.
class Solution:
def maxFrequency(self, nums: List[int], k: int) -> int:
nums = sorted(nums)
left = 0
window_sum = 0
best = 0
for right in range(len(nums)):
window_sum = window_sum + nums[right]
# cost to raise every element of the window up to nums[right]
while nums[right] * (right - left + 1) - window_sum > k:
window_sum = window_sum - nums[left] # too expensive: drop the smallest
left = left + 1
best = max(best, right - left + 1)
return best
What the code is doing, line by line:
- Sorting puts the cheapest values to raise right next to the target.
nums[right] * size - window_sumis exactly how many increments the window needs.- If that exceeds
k, drop the leftmost (smallest) element, which is the most expensive to raise.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
k = 0→ the highest existing frequency.- All equal values →
n. - Large
kthat can raise everything →n.
Complexity: O(n log n) time for the sort, O(n) for the window.
Medium 1888. Minimum Number of Flips to Make the Binary String Alternating Google Meta Microsoft
Problem: You may rotate s (move the first character to the end) any number of times, then flip characters. Return the minimum number of flips to make s alternating.
s = "111000"2s = "010"0Constraints: 1 ≤ s.length ≤ 10⁵ · s[i] is either '0' or '1'.
Hint 1
What do all rotations of s have in common with s + s?
Hint 2
Slide a window of length n over s + s, counting mismatches against both alternating patterns.
From brute force to the pattern: For each of the n rotations, counting mismatches is O(n): O(n²). In s + s consecutive rotations differ by one character in and one out, so the counts update in O(1).
Spot it: Every rotation of s is a window of length n in s + s. Compare each window with the two alternating patterns: the number of mismatches changes by at most one character in and one out as the window slides.
Why not another pattern? Rebuilding each rotation and counting mismatches is O(n²).
Approach:
- Let
t = s + s; every rotation ofsis one window of lengthnint. - Slide a window of length
novert, keeping mismatch counts against both patterns0101…and1010…. - Each full window gives a candidate: the smaller of the two counts.
class Solution:
def minFlips(self, s: str) -> int:
n = len(s)
t = s + s # every rotation is a window of length n
diff_a = 0 # mismatches against "0101..."
diff_b = 0 # mismatches against "1010..."
best = n
for i in range(len(t)):
expect_a = "0" if i % 2 == 0 else "1"
if t[i] != expect_a:
diff_a = diff_a + 1
else:
diff_b = diff_b + 1 # matching "0101..." means mismatching "1010..."
if i >= n: # the character at i - n leaves the window
old = t[i - n]
old_expect_a = "0" if (i - n) % 2 == 0 else "1"
if old != old_expect_a:
diff_a = diff_a - 1
else:
diff_b = diff_b - 1
if i >= n - 1:
best = min(best, diff_a, diff_b) # a full rotation is in the window
return best
What the code is doing, line by line:
t = s + s: rotating byrgivest[r : r + n].- The patterns are fixed to positions in
t; a window starting at an odd index simply uses them shifted, which is why both patterns are tracked. - Each character either matches pattern A or pattern B, so exactly one of the two counters goes up.
- When the window is longer than
n, the character leaving it is removed from whichever counter it was in.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Already alternating → 0.
- Odd length (rotation actually helps), e.g.
"111000"→ 2. - A single character → 0.
Complexity: O(n) time, O(n) space for s + s.
Medium 2461. Maximum Sum of Distinct Subarrays With Length K Google Amazon
Problem: Among subarrays of length k whose elements are all distinct, return the maximum sum (0 if none).
nums = [1,5,4,2,9,9,9], k = 315nums = [4,4,4], k = 30Constraints: 1 ≤ k ≤ nums.length ≤ 10⁵ · 1 ≤ nums[i] ≤ 10⁵
Hint 1
All windows have length k. How can you tell if a window has no duplicates?
Hint 2
Keep a count map of the window; it has no duplicates exactly when it has k keys.
From brute force to the pattern: Checking each window with a fresh set is O(n · k). A count map updated as the window slides is O(1) per step.
Spot it: Fixed-length windows (k) with a property of their contents (all distinct): slide the window and keep counts of the values inside.
Why not another pattern? Rebuilding a set for every window is O(n · k). A count map updated by one add and one remove per slide is O(1) per step.
Approach:
- Slide a window of size
k, keepingcountof each value and the window sum. - When a value's count drops to 0, delete it, so
len(count)is the number of distinct values. - If
len(count) == k, the window is valid: update the best sum.
class Solution:
def maximumSubarraySum(self, nums: List[int], k: int) -> int:
count = {} # value -> occurrences inside the window
window_sum = 0
best = 0
for right in range(len(nums)):
x = nums[right]
count[x] = count.get(x, 0) + 1
window_sum = window_sum + x
if right >= k:
old = nums[right - k] # the element leaving the window
count[old] = count[old] - 1
if count[old] == 0:
del count[old]
window_sum = window_sum - old
if right >= k - 1 and len(count) == k:
best = max(best, window_sum) # all k values are distinct
return best
What the code is doing, line by line:
- Add the entering element to
countand to the sum. - Once the window is longer than
k, removenums[right - k]; deleting zero counts keepslen(count)equal to the number of distinct values. - A full window with
kdistinct keys has no duplicates.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- No valid window → 0.
k = 1→ the maximum element.- A duplicate that enters and leaves the window.
Complexity: O(n) time, O(k) space.
Medium 2958. Length of Longest Subarray With at Most K Frequency Google Amazon
Problem: Return the length of the longest subarray in which every value occurs at most k times.
nums = [1,2,3,1,2,3,1,2], k = 26nums = [1,2,1,2,1,2,1,2], k = 12Constraints: 1 ≤ nums.length ≤ 10⁵ · 1 ≤ nums[i] ≤ 10⁹ · 1 ≤ k ≤ nums.length
Hint 1
When you add a new element to a valid window, which value could break the rule?
Hint 2
Shrink from the left until that value's count is at most k.
From brute force to the pattern: Counting frequencies for every subarray is O(n²). The window keeps counts incrementally and each index moves at most twice: O(n).
Spot it: Longest contiguous window satisfying a frequency limit: a variable sliding window. The only value that can exceed k after adding nums[right] is nums[right] itself, so shrink until its count is back to k.
Why not another pattern? Checking every subarray is O(n²). Binary search on the length with a check per length is O(n log n); the window is linear.
Approach:
- Add
nums[right]to the counts. - While its count exceeds
k, removenums[left]and moveleftright. - Track the longest window.
class Solution:
def maxSubarrayLength(self, nums: List[int], k: int) -> int:
count = {}
left = 0
best = 0
for right in range(len(nums)):
x = nums[right]
count[x] = count.get(x, 0) + 1
while count[x] > k:
count[nums[left]] = count[nums[left]] - 1 # shrink until x is allowed again
left = left + 1
best = max(best, right - left + 1)
return best
What the code is doing, line by line:
counttracks frequencies inside the window.- Only
xcan have just gone over the limit, so the loop shrinks untilcount[x] ≤ k. right - left + 1is the current valid window length.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
k ≥ n→ the whole array.- All elements equal →
k. k = 1→ the longest subarray of distinct values.
Complexity: O(n) time, O(n) space.
Medium 3356. Zero Array Transformation II Google
Problem: Query [l, r, val] lets you decrement each element in l..r by up to val. Return the smallest k such that the first k queries can turn nums into all zeros, or −1.
nums = [2,0,2], queries = [[0,2,1],[0,2,1],[1,1,3]]2nums = [4,3,2,1], queries = [[1,3,2],[0,2,1]]-1Constraints: 1 ≤ nums.length ≤ 10⁵ · 0 ≤ nums[i] ≤ 5 * 10⁵ · 1 ≤ queries.length ≤ 10⁵ · queries[i].length == 3 · 0 ≤ li ≤ ri < nums.length · 1 ≤ vali ≤ 5
Hint 1
Process the array from left to right. When do you need another query?
Hint 2
Keep the available decrement at index i with a difference array, and add queries one by one until it reaches nums[i].
From brute force to the pattern: Trying each k and simulating all k queries is O(q · (n + q)). Because the first k queries are always used in order, one left-to-right sweep that pulls queries only when needed finds the minimum k in O(n + q).
Spot it: Walk the array left to right keeping how much decrement is available at the current index. Whenever it's not enough, pull in the next query; queries only ever move forward, so this is a two-pointer sweep with a difference array for the ranges.
Why not another pattern? Binary search on k with a difference-array check per guess is O((n + q) log q) and also fine; the sweep avoids the log factor.
Approach:
diffrecords where each applied query's decrement starts and stops;poweris the running sum at indexi.- At index
i, adddiff[i]topower. - While
power < nums[i]: apply queryk. If it coversi, it contributes now (power += val) and stops afterr; if it starts later, record it indiff. If no queries are left, return −1. - After the sweep,
kis the answer.
class Solution:
def minZeroArray(self, nums: List[int], queries: List[List[int]]) -> int:
n = len(nums)
diff = [0] * (n + 1) # decrement starting/stopping at each index
power = 0 # total decrement available at the current index
k = 0 # number of queries used so far
for i in range(n):
power = power + diff[i]
while power < nums[i]:
if k == len(queries):
return -1 # every query used and nums[i] still can't reach 0
l, r, val = queries[k]
k = k + 1
if r < i:
continue # this query only covers indices we've already handled
start = max(l, i)
if start == i:
power = power + val # covers i right now
else:
diff[start] = diff[start] + val # starts later
diff[r + 1] = diff[r + 1] - val # stops after r
return k
What the code is doing, line by line:
poweris the sum ofvalover the applied queries that cover indexi(a running prefix sum ofdiff).- The
whilepulls in queries in order only when needed, sokends as small as possible. - A query that ends before
iis still "processed" (it counts towardk) but can't help anymore. - Queries starting at or before
iadd topowerimmediately; later ones are scheduled indiff. All of them stop atr + 1.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Already all zeros → 0.
- Not enough total decrement → −1.
- A query that ends before the index that needs it (it still counts toward
k).
Complexity: O(n + q) time, O(n) space.
Medium 1456. Maximum Number of Vowels in a Substring of Given Length Meta Microsoft
Problem: Return the maximum number of vowels in any substring of length k.
s = "abciiidef", k = 33s = "aeiou", k = 22Constraints: 1 ≤ s.length ≤ 10⁵ · s consists of lowercase English letters. · 1 ≤ k ≤ s.length
Hint 1
All windows have length k.
Hint 2
Update the count as the window slides instead of recounting.
From brute force to the pattern: Recounting each window is O(nk); sliding is O(n).
Spot it: Every window has the same length → fixed-size sliding window: one character enters and one leaves per step.
Why not another pattern? Recounting each window is O(n · k).
Approach:
- Count vowels in the first
kcharacters. - Slide:
+1ifs[i]is a vowel,−1ifs[i − k]was. - Track the maximum.
class Solution:
def maxVowels(self, s: str, k: int) -> int:
vowels = set("aeiou")
count = sum(ch in vowels for ch in s[:k]) # first window
best = count
for i in range(k, len(s)):
count += (s[i] in vowels) - (s[i - k] in vowels) # one enters, one leaves
best = max(best, count)
return best
What the code is doing, line by line:
- Booleans add as 0/1 in Python.
- Each step costs
O(1).
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
k == len(s).- No vowels → 0.
- All vowels → k.
Complexity: O(n) time, O(1) space.
Medium 187. Repeated DNA Sequences Amazon Microsoft
Problem: Return every 10-letter substring that occurs more than once.
s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT"["AAAAACCCCC","CCCCCAAAAA"]s = "AAAAAAAAAAAAA"["AAAAAAAAAA"]Constraints: 1 ≤ s.length ≤ 10⁵ · s[i] is either 'A', 'C', 'G', or 'T'.
Hint 1
Look at every substring of length 10.
Hint 2
Remember which ones you've seen.
From brute force to the pattern: Pairwise comparison is O(n²); a set of windows is linear.
Spot it: Fixed-size sliding window of 10 characters with a hash set of windows seen so far.
Why not another pattern? Comparing every pair of windows is O(n²).
Approach:
- For each start
i:w = s[i:i + 10]. - If
wis inseen, add it torepeated; else add it toseen.
class Solution:
def findRepeatedDnaSequences(self, s: str) -> List[str]:
seen, repeated = set(), set()
for i in range(len(s) - 9):
window = s[i:i + 10]
if window in seen:
repeated.add(window) # second (or later) occurrence
else:
seen.add(window)
return list(repeated)
What the code is doing, line by line:
- A set for repeats avoids listing the same sequence twice.
- A 2-bit encoding per letter (rolling hash in 20 bits) avoids slicing, but slicing 10 letters is cheap.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Shorter than 10 → [].
- Overlapping repeats (AAAAAAAAAAA).
- Same sequence many times (listed once).
Complexity: O(10n) time, O(10n) space.
Medium 1151. Minimum Swaps to Group All 1's Together Amazon
Problem: Return the minimum swaps (of any two elements) to group all 1s together.
Solve it on LeetCode ↗ (Premium)
data = [1,0,1,0,1]1data = [0,0,0,1,0]0data = [1,0,1,0,1,0,0,1,1,0,1]3Constraints: 1 ≤ data.length ≤ 10⁵ · data[i] is either 0 or 1
Hint 1
How long is the final block of ones?
Hint 2
Which window of that length needs the fewest swaps?
From brute force to the pattern: Counting each window from scratch is O(nm); sliding is O(n).
Spot it: All m ones end up in some window of length m. Every 0 inside that window needs one swap, so find the window with the most ones: a fixed-size sliding window.
Why not another pattern? Trying every target window and counting from scratch is O(n · m).
Approach:
m = sum(data).- Slide a window of size
m, tracking its ones. - Answer
m − max ones in a window.
class Solution:
def minSwaps(self, data: List[int]) -> int:
m = sum(data) # the group's final length
if m <= 1:
return 0
ones = best = sum(data[:m])
for i in range(m, len(data)):
ones += data[i] - data[i - m] # slide by one
best = max(best, ones)
return m - best # each 0 in the best window needs one swap
What the code is doing, line by line:
- Any swap can bring one outside 1 into the window, replacing a 0.
- The window with the most ones needs the fewest swaps.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- No ones or one one → 0.
- All ones → 0.
- Ones at both ends.
Complexity: O(n) time, O(1) space.
Medium 1438. Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit Amazon
Problem: Return the longest subarray in which every pair differs by at most limit.
nums = [8,2,4,7], limit = 42nums = [10,1,2,4,7,2], limit = 54Constraints: 1 ≤ nums.length ≤ 10⁵ · 1 ≤ nums[i] ≤ 10⁹ · 0 ≤ limit ≤ 10⁹
Hint 1
The condition only depends on the window's max and min.
Hint 2
Maintain both with monotonic deques while sliding.
From brute force to the pattern: All subarrays: O(n²); a sorted structure: O(n log n); two deques: O(n).
Spot it: "Longest subarray with a condition that only gets worse as it grows" → sliding window. The condition is max − min ≤ limit, so keep the window max and min in two monotonic deques.
Why not another pattern? Recomputing max and min for every window is O(n²).
Approach:
maxqdecreasing,minqincreasing (indices).- Add
right; whilemax − min > limit, moveleftand drop deque fronts that fall out. - Track the longest window.
from collections import deque
class Solution:
def longestSubarray(self, nums: List[int], limit: int) -> int:
maxq, minq = deque(), deque() # indices; values decreasing / increasing
left = best = 0
for right, x in enumerate(nums):
while maxq and nums[maxq[-1]] <= x:
maxq.pop()
maxq.append(right)
while minq and nums[minq[-1]] >= x:
minq.pop()
minq.append(right)
while nums[maxq[0]] - nums[minq[0]] > limit:
left += 1 # shrink until the window is valid
if maxq[0] < left:
maxq.popleft()
if minq[0] < left:
minq.popleft()
best = max(best, right - left + 1)
return best
What the code is doing, line by line:
- The deque fronts are always the current window's max and min.
- Each index enters and leaves each deque once.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- limit = 0 (runs of equal values).
- All equal.
- Large swings.
Complexity: O(n) time, O(n) space.
Medium 2271. Maximum White Tiles Covered by a Carpet Amazon
Problem: Tile ranges [l, r] are white (non-overlapping). Place one carpet of length carpetLen. Return the most white tiles covered.
tiles = [[1,5],[10,11],[12,18],[20,25],[30,32]], carpetLen = 109tiles = [[10,11],[1,1]], carpetLen = 22Constraints: 1 ≤ tiles.length ≤ 5 * 10⁴ · tiles[i].length == 2 · 1 ≤ li ≤ ri ≤ 10⁹ · 1 ≤ carpetLen ≤ 10⁹ · The tiles are non-overlapping.
Hint 1
Where should the carpet start?
Hint 2
Slide over the tile ranges, counting full ranges and one partial range.
From brute force to the pattern: Every start coordinate is infeasible; starting at range ends gives O(n log n).
Spot it: A best carpet can start at some range's left end (shifting it right to one never loses tiles). Then it's a sliding window over ranges: add fully covered ranges, plus the partly covered one.
Why not another pattern? Trying every start position is impossible with coordinates up to 10⁹.
Approach:
- Sort ranges.
- For each start range
i:end = l_i + len − 1; advancejover ranges ending ≤end, summing their lengths. - Add the partial overlap of range
j; then remove rangei.
class Solution:
def maximumWhiteTiles(self, tiles: List[List[int]], carpetLen: int) -> int:
tiles = sorted(tiles)
best = covered = 0
j = 0
for i, (l, r) in enumerate(tiles):
end = l + carpetLen - 1
while j < len(tiles) and tiles[j][1] <= end:
covered += tiles[j][1] - tiles[j][0] + 1 # fully under the carpet
j += 1
partial = max(0, end - tiles[j][0] + 1) if j < len(tiles) else 0
best = max(best, covered + partial)
if j > i:
covered -= r - l + 1 # the carpet moves past range i
else:
j = i + 1 # range i itself was only partly covered
return best
What the code is doing, line by line:
- Starting at a range's left end is never worse than starting in a gap or partway into a range.
- When the carpet is shorter than range
i,jstays ati, the partial coverscarpetLen, and nothing is subtracted.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Carpet longer than all tiles.
- Carpet shorter than one range.
- Big gaps.
Complexity: O(n log n) time, O(1) extra space.
Medium 2653. Sliding Subarray Beauty Amazon
Problem: For each window of size k, return its x-th smallest negative number, or 0 if there are fewer than x negatives.
nums = [1,-1,-3,-2,3], k = 3, x = 2[-1,-2,-2]nums = [-1,-2,-3,-4,-5], k = 2, x = 2[-1,-2,-3,-4]Constraints: n == nums.length · 1 ≤ n ≤ 10⁵ · 1 ≤ k ≤ n · 1 ≤ x ≤ k · -50 ≤ nums[i] ≤ 50
Hint 1
The values are small. How can you find the x-th smallest quickly?
Hint 2
Keep a count of each negative value in the window.
From brute force to the pattern: Sorting every window is O(nk log k); counting is O(50n).
Spot it: Fixed-size sliding window with a tiny value range: a count array over −50..−1 lets you find the x-th smallest negative in 50 steps.
Why not another pattern? Sorting each window is O(n · k log k).
Approach:
count[v + 50]for negatives in the window.- Slide: add the new value, remove the old one.
- Walk counts from −50 upward until
xvalues are passed.
class Solution:
def getSubarrayBeauty(self, nums: List[int], k: int, x: int) -> List[int]:
count = [0] * 50 # count[v + 50] for v in -50..-1
result = []
for i, v in enumerate(nums):
if v < 0:
count[v + 50] += 1
if i >= k and nums[i - k] < 0:
count[nums[i - k] + 50] -= 1 # leaves the window
if i >= k - 1:
seen = 0
beauty = 0
for j in range(50): # smallest negatives first
seen += count[j]
if seen >= x:
beauty = j - 50
break
result.append(beauty)
return result
What the code is doing, line by line:
- Only negatives matter, and there are just 50 possible values.
- Walking the counts in increasing value finds the
x-th smallest.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Fewer than x negatives → 0.
- All negative.
- k = 1.
Complexity: O(50 · n) time, O(1) extra space.
Medium 2762. Continuous Subarrays Amazon
Problem: Count subarrays where every pair of elements differs by at most 2.
nums = [5,4,2,4]8nums = [1,2,3]6Constraints: 1 ≤ nums.length ≤ 10⁵ · 1 ≤ nums[i] ≤ 10⁹
Hint 1
For each right end, which left ends give a valid subarray?
Hint 2
Track the window's max and min with monotonic deques.
From brute force to the pattern: Checking all subarrays is O(n²); the counting window is O(n).
Spot it: Same window as the previous problem (max − min ≤ limit with monotonic deques), but count: each valid window [left, right] adds right − left + 1 subarrays ending at right.
Why not another pattern? Checking every subarray is O(n²).
Approach:
- Slide with max/min deques; shrink while
max − min > 2. - Add
right − left + 1for eachright.
from collections import deque
class Solution:
def continuousSubarrays(self, nums: List[int]) -> int:
maxq, minq = deque(), deque()
left = count = 0
for right, x in enumerate(nums):
while maxq and nums[maxq[-1]] <= x:
maxq.pop()
maxq.append(right)
while minq and nums[minq[-1]] >= x:
minq.pop()
minq.append(right)
while nums[maxq[0]] - nums[minq[0]] > 2:
left += 1
if maxq[0] < left:
maxq.popleft()
if minq[0] < left:
minq.popleft()
count += right - left + 1 # every start in [left, right] works
return count
What the code is doing, line by line:
- If
[left, right]is valid, every shorter subarray ending atrightis too. - The total can exceed 32 bits.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- All equal → n(n + 1)/2.
- Differences exactly 2.
- n = 1.
Complexity: O(n) time and space.
Medium 3413. Maximum Coins From K Consecutive Bags Amazon
Problem: Non-overlapping segments [l, r] hold c coins per bag. Collect k consecutive bags (positions up to 10⁹) for the most coins.
coins = [[8,10,1],[1,3,2],[5,6,4]], k = 410coins = [[1,10,3]], k = 26Constraints: 1 ≤ coins.length ≤ 10⁵ · 1 ≤ k ≤ 10⁹ · coins[i] == [li, ri, ci] · 1 ≤ li ≤ ri ≤ 10⁹ · 1 ≤ ci ≤ 1000 · The given segments are non-overlapping.
Hint 1
Coordinates are up to 10⁹, but there are at most 10⁵ segments. Where can an optimal window start or end?
Hint 2
Try windows starting at each segment's left end (and, mirrored, ending at each right end).
Hint 3
Slide over segments with two pointers, counting full segments and one partial one.
From brute force to the pattern: Sliding over every coordinate is infeasible; aligning windows to segment ends gives O(n log n).
Spot it: Positions are huge, but an optimal window can always be shifted until it starts at a segment's left end or ends at a segment's right end without losing coins. So try windows aligned to segments with a sliding window over segments (full segments summed, one partial), once forwards and once on the mirrored line.
Why not another pattern? Sliding over every position is impossible with coordinates up to 10⁹.
Approach:
best(segs): segments sorted by start; for each segmenti, the window is[l_i, l_i + k − 1].- Advance
jwhile segmentjends inside the window, adding its full coins; add the partial overlap of segmentj. - Remove segment
iwhen moving on. Run it on the segments and on their mirror images(−r, −l).
class Solution:
def maximumCoins(self, coins: List[List[int]], k: int) -> int:
def best(segs): # windows that start at a segment's left end
segs.sort()
res = full = 0
j = 0
for i, (l, r, c) in enumerate(segs):
if j < i:
j, full = i, 0 # previous window didn't even cover segment i
end = l + k - 1
while j < len(segs) and segs[j][1] <= end:
full += (segs[j][1] - segs[j][0] + 1) * segs[j][2] # segment fully inside
j += 1
partial = 0
if j < len(segs) and segs[j][0] <= end:
partial = (end - segs[j][0] + 1) * segs[j][2] # cut-off segment at the end
res = max(res, full + partial)
if j > i:
full -= (r - l + 1) * c # slide past segment i
return res
forward = best([s[:] for s in coins])
backward = best([[-r, -l, c] for l, r, c in coins]) # windows that end at a right end
return max(forward, backward)
What the code is doing, line by line:
- If a window starts inside a gap or mid-segment, shifting it toward the richer side never loses coins until an endpoint aligns; that's why only aligned windows are needed.
- Mirroring positions (
x → −x) turns "ends at a right end" into "starts at a left end", so one helper handles both. fullholds the coins of segments completely inside the window; at most one segment is cut at the right edge.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- k longer than all segments combined.
- One very long segment (window inside it).
- Gaps between segments.
Complexity: O(n log n) time, O(n) space.
Hard 1703. Minimum Adjacent Swaps for K Consecutive Ones Amazon
Problem: Return the fewest adjacent swaps to make k consecutive 1s.
nums = [1,0,0,1,0,1], k = 21nums = [1,0,0,0,0,0,1,1], k = 35Constraints: 1 ≤ nums.length ≤ 10⁵ · nums[i] is 0 or 1. · 1 ≤ k ≤ sum(nums)
Hint 1
Only the positions of the ones matter. Which k ones should end up together?
Hint 2
If you subtract each one's rank from its position, what does "consecutive" become?
Hint 3
Moving points to a common value costs least at the median; use prefix sums for each window.
From brute force to the pattern: Trying every window and simulating swaps is O(nk); the median formula is O(n).
Spot it: Choose k consecutive ones (a sliding window over the ones' positions) and gather them together. Gathering into a block is like gathering to one point after the shift q_i = p_i − i; the best meeting point is the median, and prefix sums give the cost of each window in O(1).
Why not another pattern? Simulating swaps for every window is O(n · k) or worse.
Approach:
pos= indices of ones;q[i] = pos[i] − i; prefix sumsPofq.- For each window
q[i..i+k−1]with median indexm = i + k // 2: cost =q[m]·(m − i) − (P[m] − P[i]) + (P[i+k] − P[m+1]) − q[m]·(i + k − m − 1). - Return the minimum.
class Solution:
def minMoves(self, nums: List[int], k: int) -> int:
q = [p - i for i, p in enumerate(i for i, x in enumerate(nums) if x)] # positions minus rank
P = [0]
for v in q:
P.append(P[-1] + v)
best = float("inf")
for i in range(len(q) - k + 1):
m = i + k // 2 # median of this window
left = q[m] * (m - i) - (P[m] - P[i]) # pull the left part up to the median
right = (P[i + k] - P[m + 1]) - q[m] * (i + k - m - 1) # pull the right part down
best = min(best, left + right)
return best
What the code is doing, line by line:
- Subtracting each one's rank turns "make them consecutive" into "move them all to the same value", which costs
Σ|q − target|. - That sum is minimised at the median, and with prefix sums it's computed per window in
O(1). - Windows of consecutive ones are optimal: skipping a one in between would only add distance.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- k = 1 → 0.
- Ones already together → 0.
- Ones spread far apart.
Complexity: O(n) time, O(n) space.
Hard 2009. Minimum Number of Operations to Make Array Continuous Amazon
Problem: Replace elements so the array has distinct values with max − min = n − 1. Return the fewest replacements.
nums = [4,2,5,3]0nums = [1,2,3,5,6]1Constraints: 1 ≤ nums.length ≤ 10⁵ · 1 ≤ nums[i] ≤ 10⁹
Hint 1
The final array is a block of n consecutive values. Which existing values can you keep?
Hint 2
Sort the distinct values and slide a window of width n − 1.
From brute force to the pattern: Trying every block start is infeasible; a window over sorted distinct values is O(n log n).
Spot it: The final values are a block [v, v + n − 1]. Keep as many existing distinct values inside some such block as possible: over the sorted distinct values, a sliding window where vals[right] − vals[left] ≤ n − 1.
Why not another pattern? Trying every starting value v up to 10⁹ is impossible.
Approach:
vals = sorted(set(nums)).- For each
left, extendrightwhilevals[right] ≤ vals[left] + n − 1. - Answer
n − max(window size).
class Solution:
def minOperations(self, nums: List[int]) -> int:
n = len(nums)
vals = sorted(set(nums)) # duplicates must be replaced anyway
best = right = 0
for left in range(len(vals)):
while right < len(vals) and vals[right] <= vals[left] + n - 1:
right += 1 # still fits in a block starting at vals[left]
best = max(best, right - left) # values we can keep
return n - best
What the code is doing, line by line:
- Starting the block at an existing value is never worse.
- Values kept must be distinct and fit in the block; everything else is replaced.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Already continuous → 0.
- Many duplicates.
- Values far apart.
Complexity: O(n log n) time, O(n) space.
Medium 2067. Number of Equal Count Substrings Cisco
Problem: Count substrings in which every letter that appears occurs exactly count times.
Solve it on LeetCode ↗ (Premium)
s = "aaabcbbcc", count = 33s = "abcd", count = 20s = "a", count = 50Constraints: 1 ≤ s.length ≤ 3 * 10⁴ · 1 ≤ count ≤ 3 * 10⁴ · s consists only of lowercase English letters
Hint 1
If a valid substring has u distinct letters, how long is it?
Hint 2
For each u, slide a window of that length.
From brute force to the pattern: All substrings with counts: O(26 n²); one fixed window per u: O(26 n).
Spot it: If such a substring has u distinct letters, its length is exactly u · count. So for each u from 1 to 26, a fixed-size sliding window with letter counts, tracking how many letters sit at exactly count.
Why not another pattern? Checking every substring's letter counts is O(26 n²).
Approach:
- For
u = 1..26: window lengthL = u · count(skip if > n). - Slide, updating counts and
exact= letters with count ==count. - When
exact == u, the window qualifies.
class Solution:
def equalCountSubstrings(self, s: str, count: int) -> int:
n = len(s)
total = 0
for u in range(1, 27): # number of distinct letters in the substring
L = u * count
if L > n:
break
freq = [0] * 26
exact = 0 # letters occurring exactly `count` times in the window
for i, ch in enumerate(s):
c = ord(ch) - 97
freq[c] += 1
if freq[c] == count:
exact += 1
elif freq[c] == count + 1:
exact -= 1
if i >= L:
o = ord(s[i - L]) - 97 # character leaving the window
if freq[o] == count:
exact -= 1
freq[o] -= 1
if freq[o] == count:
exact += 1
if i >= L - 1 and exact == u:
total += 1 # u letters, each exactly count times, fill all L positions
return total
What the code is doing, line by line:
- A window of length
u · countwithuletters at exactlycounthas no room for any other letter. - Only the entering and leaving letters change, so each step is
O(1).
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- count larger than the string → 0.
- Single letter repeated.
- All 26 letters.
Complexity: O(26 · n) time, O(1) space.
Medium 1208. Get Equal Substrings Within Budget Microsoft
Problem: Changing s[i] to t[i] costs |s[i] − t[i]|. Return the longest substring you can make equal within maxCost.
s = "abcd", t = "bcdf", maxCost = 33s = "abcd", t = "cdef", maxCost = 31Constraints: 1 ≤ s.length ≤ 10⁵ · t.length == s.length · 0 ≤ maxCost ≤ 10⁶ · s and t consist of only lowercase English letters.
Hint 1
Turn the problem into an array of costs.
Hint 2
Longest subarray with sum ≤ maxCost: sliding window.
From brute force to the pattern: All substrings: O(n²); sliding window: O(n).
Spot it: Costs are non-negative, so a longer window never costs less → variable sliding window.
Why not another pattern? Checking every substring is O(n²).
Approach:
- Extend
right, adding its cost. - While the total exceeds
maxCost, removeleft's cost and moveleft. - Track the longest window.
class Solution:
def equalSubstring(self, s: str, t: str, maxCost: int) -> int:
left = total = best = 0
for right in range(len(s)):
total += abs(ord(s[right]) - ord(t[right]))
while total > maxCost:
total -= abs(ord(s[left]) - ord(t[left])) # shrink from the left
left += 1
best = max(best, right - left + 1)
return best
What the code is doing, line by line:
- Non-negative costs make the window monotone.
- Each index enters and leaves once.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- maxCost = 0 (only equal characters).
- Everything affordable.
- Nothing affordable → 0.
Complexity: O(n) time, O(1) space.
Hard 220. Contains Duplicate III Netflix
Problem: Return whether two indices i ≠ j exist with |i − j| ≤ indexDiff and |nums[i] − nums[j]| ≤ valueDiff.
nums = [1,2,3,1], indexDiff = 3, valueDiff = 0truenums = [1,5,9,1,5,9], indexDiff = 2, valueDiff = 3falseConstraints: 2 ≤ nums.length ≤ 10⁵ · -10⁹ ≤ nums[i] ≤ 10⁹ · 1 ≤ indexDiff ≤ nums.length · 0 ≤ valueDiff ≤ 10⁹
Hint 1
The index condition is a sliding window.
Hint 2
Group values into buckets of width valueDiff + 1. Where can a close value be?
From brute force to the pattern: Check all pairs within indexDiff: O(n · indexDiff); buckets make it O(n).
Spot it: The index limit is a sliding window of the last indexDiff values. To find a close value inside it in O(1), put values into buckets of width valueDiff + 1 (a hash map bucket → value): two values in one bucket are close, and a match can otherwise only be in a neighbouring bucket.
Why not another pattern? Comparing each value with the whole window is O(n · indexDiff); a sorted container works in O(n log k) but buckets are O(n).
Approach:
w = valueDiff + 1; bucket idx // w(floor division handles negatives).- For each value: if its bucket is occupied → true; if a neighbouring bucket holds a value within
valueDiff→ true. - Store the value in its bucket; drop
nums[i − indexDiff]'s bucket so the window staysindexDiffwide.
class Solution:
def containsNearbyAlmostDuplicate(self, nums: List[int], indexDiff: int, valueDiff: int) -> bool:
width = valueDiff + 1 # any two values in one bucket differ by at most valueDiff
buckets = {} # bucket id -> the value in the window that falls there
for i, x in enumerate(nums):
b = x // width
if b in buckets:
return True
if b - 1 in buckets and x - buckets[b - 1] <= valueDiff:
return True
if b + 1 in buckets and buckets[b + 1] - x <= valueDiff:
return True
buckets[b] = x
if i >= indexDiff:
del buckets[nums[i - indexDiff] // width] # slide the window: that index is now too far back
return False
What the code is doing, line by line:
- Each bucket holds at most one value: a second one would already have returned true.
- A close value is either in the same bucket or in a neighbour; anything two buckets away differs by more than
valueDiff. - Removing the value from
indexDiffpositions back keeps only indices within range.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- valueDiff = 0 (exact duplicates).
- Negative numbers (floor division).
- indexDiff ≥ n.
Complexity: O(n) time, O(min(n, indexDiff)) space.