At Most K Distinct
What is this?
You are walking a street of food stalls with two baskets, picking up fruit as you go, and you may only ever carry two kinds. You never skip a stall and never walk backwards — when a third kind appears, you drop fruit from the back of your haul until only two kinds remain. The longest unbroken stretch you managed is the answer.
That is the entire technique, and it is worth noticing that the rule is about the number of kinds, not the amount. You can carry twenty apples; you cannot carry three kinds.
💡 Fun fact: this template is the answer to a startling number of differently-worded problems — Longest Substring Without Repeating Characters is
k = 1per character, Fruit Into Baskets isk = 2wearing a costume, and Subarrays With K Different Integers is this function called twice and subtracted. When you meet a new window problem, the useful first question is not "how do I solve this" but "is this at-most-k in disguise?"
🔓 The 3 problems in this chapter are free. Sign in with Google or Microsoft to start solving.
The one-line idea: grow the window every step, shrink it while it holds more than
kdistinct values, and record the length whenever it is valid. The distinct count islen(map), which is only true if you delete keys the moment their count reaches zero.
1. The template
from collections import defaultdict
def longest_at_most_k(s: str, k: int) -> int:
count = defaultdict(int)
left = best = 0
for right, ch in enumerate(s):
count[ch] += 1
while len(count) > k: # while, not if
out = s[left]
count[out] -= 1
if count[out] == 0:
del count[out] # or len(count) lies
left += 1
best = max(best, right - left + 1)
return best
Six lines, two of which are the whole difficulty:
while, not if. With this particular invariant a single arrival adds at most one new key, so one shrink step happens to suffice — but write while anyway. The moment the condition becomes "sum ≤ target" or "at most k odd numbers", one arrival can break it by more than one element, and an if fails silently on inputs you will not think to test.
Delete at zero. len(count) is the distinct-value count only if exhausted keys are removed. Leave a key sitting at zero and the window believes it is still carrying a fruit it dropped, so it shrinks further than it should and every answer comes back short.
2. Why it is O(n) despite the nested loop
left never decreases and never passes right. Across the entire run it advances at most n times in total, so the inner while performs at most n iterations summed over all outer steps — not per step. Each element is added exactly once and removed at most once.
This amortisation argument is what interviewers listen for. "It's a sliding window so it's O(n)" is a memorised phrase; "both pointers are monotonic, so each element enters and leaves once" is an explanation.
3. A 30-second worked example
s = "eceba", k = 2.
Two things to notice. The shrink at right = 3 ran twice, because dropping 'e' left three kinds still present — the clearest possible argument for while over if. And 'c' was deleted from the map at that moment, which is what let len(count) fall back to 2.
4. Where you'll actually meet this
- Session and cohort analysis. "Longest run of events involving at most k distinct users/devices" is this loop over a log.
- Cache and working-set sizing. The longest stretch of a trace touching at most
kdistinct pages is exactly the question that motivates LRU cache capacity choices. - Bandwidth and connection limits. Longest period during which a client contacted no more than
kdistinct endpoints — a real anomaly-detection signal. - Text and DNA analysis. Longest substring over a restricted alphabet, used in read filtering and low-complexity region detection.
- Inventory constraints. The literal fruit-baskets framing: how much can you collect from consecutive stalls with limited container types.
5. Problems in this chapter
▶ Longest Substring with At Most Two Distinct Characters
The template with k hard-coded to 2. Worth solving first precisely because the small k makes it easy to trace by hand and see the deletion matter.
Pattern: grow-and-shrink with a count map. Target: O(n) time, O(1) space (at most 3 keys live).
▶ Longest Substring with At Most K Distinct Characters
The same function with k as a parameter. The generalisation is free — which is the point — and it is the version worth memorising, since "exactly k" and its relatives are built from it.
Pattern: grow-and-shrink with a count map. Target: O(n) time, O(k) space.
6. Common pitfalls 🚫
ifinstead ofwhile. Correct for this invariant, wrong the moment the invariant changes — and the habit is what carries you between problems.- Not deleting zero-count keys. The single most common bug here; the code runs, returns plausible numbers, and is wrong on inputs where a character leaves and returns.
- Using
sum(count.values())to test distinctness. That is the window's length, not its variety. - Recording the answer inside the shrink loop. Measure only after the window is valid again.
- Returning the substring when the length was asked for (or vice versa) — check which, and remember that reconstructing the substring needs you to store
leftat the best moment. - Assuming
kis sane.k = 0should return 0, not loop forever; a one-line guard costs nothing. - Reaching for this when the constraint is not monotonic. Growing the window must only ever increase the distinct count. If the condition can improve as the window grows, this template does not apply.
- Max Consecutive Ones II — longest run of
1s if you may flip a single0. The same grow-and-shrink loop with the bound counting zeros instead of distinct values, which is what makes this template a family rather than one problem.
7. Key takeaways
- Grow always, shrink while invalid, measure when valid. That order is the template.
len(map)is the distinct count only if you delete at zero.- Write
while, notif— it costs nothing here and saves you on the next problem. - O(n) comes from monotonic pointers, not from the technique's name. Be able to say why.
- This template is a family, not a problem. Recognise
k = 1,k = 2and "exactly k" variants as the same code. - Why interviewers like it: it is short enough to write in three minutes, which means the whole conversation is about your invariant, your amortisation argument, and whether you noticed the deletion.
Order: Longest Substring with At Most Two Distinct Characters → Longest Substring with At Most K Distinct Characters → Max Consecutive Ones II.
Max Consecutive Ones II
Core idea: You want the longest run of
1s in a binary array, but you're allowed to flip at most one0into a1. The trick: a run of1s with one flipped0is exactly a contiguous window that contains at most one0. So slide a variable-size window with two pointers, expanding the right edge greedily. The instant the window swallows a second0, shrink the left edge forward — past the older of the two zeros — until the window is back down to a single0. The window's length is always "a run after one flip"; the largest length you ever see is the answer. Each pointer only moves right, every element is visited at most twice → O(n) time, O(1) space.
The problem, rephrased
You're monitoring a fault-tolerant streak on a manufacturing line. Each unit rolls off as 1 (passed inspection) or 0 (a defect). Management celebrates the longest unbroken run of passes — and they'll forgive one single defect, treating it as if it had passed, to keep a streak alive. What's the longest run you can claim if you're allowed to pardon at most one defect?
Formally: given a binary array nums (each element 0 or 1), return the maximum number of consecutive 1s you can obtain if you may flip at most one 0 to a 1.
This is LeetCode 487 — Max Consecutive Ones II.
| Input | Means | Output |
|---|---|---|
nums = [1,0,1,1,0] |
Flip either 0; best window spans 1,0,1,1 (flip index 1) |
4 |
nums = [1,1,1,1] |
No zeros to flip; the whole array is already a run | 4 |
nums = [0,0,0] |
Flip one 0; you get a single 1 surrounded by zeros |
1 |
The "at most one" is the whole story. If you could flip zero zeros this is just the trivial "longest run of 1s." If you could flip any number, the answer is just the array length. Exactly one allowed flip is what turns this into a sliding-window puzzle: the window may hold one zero, never two.
Sign in to continue reading
The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.
Sign in to read the full lesson