🚀 Phases 1–5 are live — Days 1–17 cover the foundations and the algorithmic patterns. See the roadmap →
Day 24 - Sliding WindowThe At-Most-K Trick

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 nums and integer k, return the number of good subarrays of nums. A good subarray is a contiguous subarray with exactly k distinct 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 nums and an integer k. A continuous subarray is called nice if there are k odd 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 nums and integer goal, return the number of non-empty subarrays with sum equal to goal.

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:

  1. The problem asks for “exactly K” of some countable property over a contiguous range.
  2. 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.)
  3. 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

Why does atMost(k) - atMost(k - 1) equal 'exactly k'?
What property must hold for the at-most-K trick to work?
For Subarrays with K Different Integers using atMost(k) - atMost(k - 1), what's the total time complexity?

Next: advanced patterns — monotonic-deque windows, two-pointer techniques, and string-frequency-matching variants of sliding window.