The At-Most-K Trick
Some problems ask for exactly a property — exactly K distinct integers, exactly K odd numbers, exactly K vowels. Sliding window doesn’t naturally count “exactly” — the window can drift past the target and back. But there’s a clean indirect move:
Count of subarrays with exactly K = Count with at most K − Count with at most (K − 1).
The “at most” version is a textbook variable-window problem. So you solve the same template twice with different k, subtract, done.
This trick is so common in interviews that recognizing it is half the battle.
Why does it work?
Define atMost(k) = number of contiguous subarrays whose property count is <= k.
Then:
atMost(K)= subarrays with property count in{0, 1, 2, ..., K}.atMost(K - 1)= subarrays with property count in{0, 1, 2, ..., K - 1}.- Subtraction = subarrays with property count exactly
K.
It’s set subtraction. The “at most” function is monotone (the answer for k is always >= the answer for k - 1), so the subtraction is always non-negative.
Worked example 1: Subarrays with K Different Integers
Given an integer array
numsand integerk, return the number of good subarrays ofnums. A good subarray is a contiguous subarray with exactlykdistinct integers.
Direct sliding window for “exactly k” is awkward — you’d have to enumerate every (l, r) and count. But atMost(k) is a standard variable-window count.
Step 1 — write atMost(k)
State: frequency map. Invalid when: len(map) > k. Count: r − l + 1 for each r once the window is valid.
Step 2 — return the difference
atMostK(k) − atMostK(k - 1). That’s it.
Time: O(n) (two passes). Space: O(k).
Worked example 2: Count Number of Nice Subarrays
Given an array of integers
numsand an integerk. A continuous subarray is called nice if there arekodd numbers in it. Return the number of nice subarrays.
Same shape — “exactly k odd numbers.” Solve as atMost(k) − atMost(k - 1), where “at most k odd” is the windowed count.
Same atMost template, different running statistic (count of odd numbers instead of distinct-integer count). The two-call pattern is identical.
Worked example 3: Binary Subarrays With Sum
Given a binary array
numsand integergoal, return the number of non-empty subarrays with sum equal togoal.
Same trick: “exactly goal ones in the window.”
The if S < 0: return 0 guard handles the edge case where goal == 0 and we call at_most(-1).
When the trick applies
The at-most-K trick works whenever:
- The problem asks for “exactly K” of some countable property over a contiguous range.
- The property’s count over a window is monotone in window growth — adding an element can only increase or preserve the count, never decrease it. (Distinct values, odd numbers, sum of non-negative numbers, character frequencies — all monotone. Things like “max minus min” or signed sums are not.)
- The
atMost(k)version of the problem is a clean variable-size sliding window.
If all three hold, the trick is a near-immediate O(n) solution.
When the trick doesn’t apply
It fails when:
- The property isn’t monotone (signed sums, max − min with mixed values).
- The “at most” version isn’t easily slidable (e.g., the count depends on the window’s order, not just its multiset).
- You’re asked for “exactly K” of something where K = 0 is the natural empty answer (e.g., “no duplicates”); the trick still works but is overkill.
A cousin: prefix-sum + hash map
For “subarrays with sum equal to K” on signed integers (where sliding window doesn’t work because adding can decrease the sum), the standard fallback is prefix sums + hash map, which is O(n) time and O(n) space.
The at-most-K trick is the right tool for non-negative or counting properties. The prefix-sum trick is the right tool when the underlying quantity can decrease as the window grows. Know both.
The pattern in code, one more time
The reusable template:
def exactly_k_subarrays(nums, k, property_count):
"""
property_count(nums, K) = # subarrays where (some property count) <= K.
Returns: # subarrays where the property count is EXACTLY k.
"""
return property_count(nums, k) - property_count(nums, k - 1)For any problem where the property count is monotone over window growth, write at_most(K) as a 10-line variable-size sliding window, then return the subtraction. Two lines of “main” code, one slid template.
Quick check
Next: advanced patterns — monotonic-deque windows, two-pointer techniques, and string-frequency-matching variants of sliding window.