Windows with a hash map
In one line
When validity depends on which characters or values are inside the window, keep their counts in a hash map so each step updates in O(1) and the whole scan stays O(n).
What it is
Many window questions are about the contents of the window, not its sum: no repeated characters, at most k distinct values, contains every letter of a pattern. A hash map (dictionary) from value to count, or value to last index, describes those contents.
When an element enters, increment its count; when it leaves, decrement it (and delete it at zero if you track the number of distinct keys with len(map)). Combined with the grow-and-shrink loop of a variable window, this solves classic problems such as Longest Substring Without Repeating Characters and Minimum Window Substring in O(n).
Why interviewers ask
These are some of the most frequently asked string questions at large tech companies, and Minimum Window Substring is a well-known hard problem. They test whether you can define exactly when a window is valid, keep a cheap summary of that (like a 'missing' counter), and avoid comparing whole maps on every step, which would add a factor of the alphabet size.
How it works
- 1Decide what the map stores: counts of each value in the window, how many of each character you still need, or the last index where each character was seen.
- 2Grow: add s[right] and update its entry in O(1).
- 3Keep a single number that tells you if the window is valid, for example 'number of distinct keys', 'characters still missing' or 'does the new character already appear'. Update it only when a count crosses an important boundary (0 to 1, 1 to 0).
- 4Shrink: while the window is invalid (for longest problems) or valid (for shortest problems), remove s[left], update its count and the summary number, then move left.
- 5Shortcut for 'no repeats': store last seen index; when s[right] was seen inside the window, jump left directly to last index + 1. For 'abcabcbb' this gives 3 ('abc').
Worked example · Minimum window of s = 'aabcb' containing every letter of t = 'abc'
- Start: need = {a: 1, b: 1, c: 1}, missing = 3, left = 0.
- right = 0 'a': need[a] was 1, so missing = 2; need[a] = 0. right = 1 'a': need[a] was 0 (not needed), so missing stays 2; need[a] = -1 (one extra 'a').
- right = 2 'b': missing = 1, need[b] = 0. right = 3 'c': missing = 0, need[c] = 0. The window 'aabc' (0..3, length 4) covers t: best = 'aabc'.
- Shrink: remove s[0] 'a': need[a] goes -1 to 0, not above 0, so nothing is missing; left = 1. Window 'abc' (1..3, length 3) is still valid: best = 'abc'.
- Remove s[1] 'a': need[a] = 1 > 0, so missing = 1; left = 2. Invalid, stop shrinking.
- right = 4 'b': need[b] goes 0 to -1, missing stays 1. Scan ends. Answer 'abc'.
Picture it
It is like a shopping list while walking down a market aisle: you tick items off as you pass stalls (right), and you return items from the front of your basket when you have extras (left). A single number, 'items still missing', tells you when the list is complete without re-reading it.
Reference code
from collections import Counter
def longest_unique(s):
last = {} # char -> last index seen
left = best = 0
for right, ch in enumerate(s):
if ch in last and last[ch] >= left:
left = last[ch] + 1 # jump past the earlier copy
last[ch] = right
best = max(best, right - left + 1)
return best
def min_window(s, t):
need = Counter(t) # how many of each char we still owe
missing = len(t) # total chars still owed
left, best = 0, (0, float("inf"))
for right, ch in enumerate(s):
if need[ch] > 0:
missing -= 1
need[ch] -= 1
while missing == 0: # window covers t: shrink it
if right - left < best[1] - best[0]:
best = (left, right)
need[s[left]] += 1
if need[s[left]] > 0: # we just gave up a needed char
missing += 1
left += 1
return "" if best[1] == float("inf") else s[best[0]:best[1] + 1]
longest_unique uses a last-index map and jumps left directly: O(n) time, O(alphabet) space. min_window uses a need map and a single missing counter, so validity is checked in O(1) instead of comparing whole maps: O(len(s) + len(t)) time.
Key patterns
No repeats
When: Longest substring without repeating characters
How: Map char to last index; if it was seen at or after left, jump left to last index + 1.
At most k distinct
When: Longest substring with at most k distinct characters, fruit into baskets (k = 2)
How: Count map; delete a key when its count hits 0; shrink while len(map) > k.
Cover a pattern
When: Minimum window substring, smallest range containing all required items
How: need map plus a 'missing' counter; shrink while missing == 0, recording the best.
Replace at most k
When: Longest repeating character replacement
How: Track the highest count of one letter in the window; valid while length - max_count <= k. The max count never needs to decrease for the answer to stay correct.
Real-world use
Search snippets and highlighting
Search engines show a short snippet of a page that contains all your query words. Finding the shortest passage containing every term is the minimum window problem over words instead of characters.
Fraud and abuse detection
Systems flag an account when, within a window of recent events, it touches more than k distinct cards, devices or IP addresses. A count map per window gives the number of distinct items in O(1) per event.
Network intrusion detection
Tools in the style of Snort or Zeek watch how many distinct ports one source contacts within a recent window of packets; many distinct ports in a short window suggests a port scan.
Data deduplication and streaming
Stream processors track the last time each key was seen to drop duplicate events that arrive within a time window, the same 'last seen index' trick used for no-repeat substrings.
In a project
You build a 'smart highlight' feature for a documentation site: for a search with several keywords, show the shortest passage of the page that contains all of them.
- Tokenise the page into words and lower-case them; build need = counts of the query keywords and missing = number of keywords.
- Slide right over the words, decrementing need for each word and reducing missing when a needed keyword's count goes from 1 to 0.
- When missing is 0, shrink from the left while the window stays valid, recording the shortest one.
- Return that word range, expanded by a few words on each side for context, and highlight the keywords inside it.
Impact: Snippets go from 'first 200 characters of the page' to the passage that actually answers the query, computed in O(n) per page even for long documents, which keeps search results fast.
Trade-offs
Use it when
- Window validity depends on the identity of elements (repeats, distinct counts, required letters)
- Values come from a large or unknown set, so a fixed array is not practical
- You need O(n) and can describe validity with one counter derived from the map
Avoid it when
- The alphabet is small and known (26 lowercase letters): a fixed-size array is faster and simpler
- Validity depends on order inside the window rather than counts
- The data is not contiguous (subsequence problems)
Cost: Time O(n), each element enters and leaves once, with O(1) average hash map operations. Space O(k) where k is the number of distinct values in the window (at most the alphabet size).
Common mistakes
- Comparing the whole window map with the target map on every step, turning O(n) into O(n * alphabet) or worse.
- Not deleting keys whose count reaches 0, so len(map) overcounts the distinct values.
- In the last-index trick, jumping left backwards: only jump if the last index is at or after left (use max or a check).
- Updating the 'missing' counter on every change instead of only when a needed count crosses zero.
- Off-by-one when slicing the answer: s[start:end + 1].
Interview questions
Quick revision
- Map tracks window contents: value -> count or value -> last index.
- Summarise validity in one number: distinct count or missing count.
- Delete map keys when the count reaches 0 if you use len(map).
- No repeats: left = max(left, last[ch] + 1).
- Minimum window: expand until missing == 0, then shrink and record.
- Time O(n), space O(distinct values).
- Small fixed alphabet: use a 26-length array instead of a dict.
Check yourself
1. What is the length of the longest substring without repeating characters in 'pwwkew'?
2. In the minimum window algorithm, when does the 'missing' counter decrease?
3. You track distinct values with len(count_map). What must you do when a count drops to 0?
4. What is the time complexity of the longest substring without repeats using a last-index map?
Related
Saved on this device. Create a free account to keep it on every device.
Practise this with Sam
A real mock interview, out loud, with an honest scorecard. Free to start.