Length of Longest Subarray With at Most K Frequency
š Full written solution: https://interview-kit-fe.vercel.app/length-of-longest-subarray-with-at-most-k-frequency š» Code on GitHub: https://github.com/timepasshub2539-hue/leetcode-python/tree/main/length-of-longest-subarray-with-at-most-k-frequency Chapters: 0:00 The Problem 0:19 Break This Rule Once 0:33 Fun with Learning Technology 0:47 Where This Shows Up 0:58 A Tiny Example 1:13 The Tools We'll Use 1:26 Brute Force on the Example 1:41 Where Brute Force Breaks 1:55 The Growing Window Trick 2:07 Counting With a Dictionary 2:17 Walking Through the Example 2:31 In Short 2:40 Why This Never Re-Scans 2:51 Big O of N 3:01 Turning the Idea Into Steps 3:15 The Code: Setup 3:24 Growing and Shrinking the Window 3:36 Tracking the Best Answer 3:45 The Full Solution 3:52 Dry Run: Alternating Pattern 4:05 Pause: All Same Elements 4:14 The Answer: Four 4:23 A Realistic Mistake 4:34 Alternative: Brute Force 4:47 Complexity of Our Solution 4:57 The Pattern You'll Reuse 5:25 Recap: The Growing Window 5:57 Quick Quiz 6:05 ... 6:06 Answer 6:14 Round 2 6:21 ... 6:22 Answer š Full written solution: https://interview-kit-fe.vercel.app/length-of-longest-subarray-with-at-most-k-frequency š» Code on GitHub: https://github.com/timepasshub2539-hue/leetcode-python/tree/main/length-of-longest-subarray-with-at-most-k-frequency Chapters: 0:00 The Problem 0:19 The Rule You Must Not Break 0:33 Meet Kai 0:47 Real-World Use Cases 0:58 A Small Example 1:13 Sliding Window Tools 1:26 Brute Force Walkthrough 1:41 Where Brute Force Fails 1:55 The Growing Window Trick 2:07 Counting With a Dictionary 2:17 Example Walkthrough 2:31 Quick Recap 2:40 Why It Never Re-Scans 2:51 O(N) Time Complexity 3:01 Turning the Idea Into Steps 3:15 Code: Setup 3:24 Code: Grow and Shrink 3:36 Code: Tracking the Best 3:45 Full Python Solution 3:52 Dry Run: k Equals One 4:05 Edge Case: All Same Elements 4:14 The Answer Is Four 4:23 A Common Mistake 4:34 Brute Force Alternative 4:47 Time and Space Complexity 4:57 The Reusable Pattern 5:25 Recap: Growing Window 5:57 Quick Quiz 6:05 ... 6:06 Answer 6:14 Round 2 6:21 ... 6:22 Answer We solve 2958. Length of Longest Subarray With at Most K Frequency with a single growing-and-shrinking window, tracked entirely by one dictionary ā no re-scanning, no nested loops. Walk through a tiny example, see exactly where brute force breaks, then dry-run the code on 1,2,1,2,1,2,1,2 with k=1 and the 5,5,5,5,5,5,5 edge case with k=4. Ends with a quick quiz to lock in the pattern. #leetcode #python #coding #dsa #softwareengineering Watch next: - How to See Previous Rows WITHOUT Grouping #shorts: https://youtu.be/j-gfB9Gh3ns - Lesson 18: Stop Grouping! See Previous Rows Without Collapsing Data: https://youtu.be/jH5XvUvhoLw - Lesson 15: Breaking the GPS Problem: Matching Coordinates Without Errors: https://youtu.be/iGUYRF3Yeuo
Introduction
Every so often a problem shows up that looks like it needs a fancy data structure ā a heap, a sorted map, maybe even a segment tree ā and then turns out to be a two-pointer exercise wearing a disguise. Length of Longest Subarray With at Most K Frequency (LeetCode 2958) is exactly that kind of problem, and it's a favorite in interviews for a specific reason: it tests whether you actually understand why sliding window works, not just whether you've memorized the template.
The core idea ā find the longest stretch of a sequence where no value repeats too often ā isn't just an interview trick. It's the same shape behind rate limiters, spam filters, and anomaly-detection windows in real production systems. Once you internalize this pattern, you'll start recognizing it everywhere: "longest/shortest run under a running constraint" is a signal to reach for two pointers and a count map, before anything more exotic.
Problem Overview
You're given an array of integers and an integer k. You need to find the length of the longest contiguous subarray such that no single value appears more than k times inside it.
In plain terms: slide a window across the array, and as long as every distinct number inside that window shows up k times or fewer, the window is "legal." Your job is to find the biggest legal window that ever existed as you swept across the array.
Example
Consider the array:
[1, 2, 3, 1, 2, 3, 1, 2], k = 2
The first six elements ā [1, 2, 3, 1, 2, 3] ā form a valid window. Each value (1, 2, 3) appears exactly twice, which is within the limit of k = 2.
Add the seventh element (1), and now 1 appears three times. The window is no longer legal, so it has to shrink from the left until it's valid again.
The answer for this array is 6 ā the largest legal window we found.
Intuition
The naive instinct is: "just check every possible subarray." Pick a start, pick an end, count the frequency of every element in between, and see if it's legal. That works, but think about what happens as you slide the end pointer forward by one position: almost nothing about the window changed. You added exactly one value. Yet the brute-force approach throws away everything it knew and recounts from scratch.
That's the real waste ā not the comparison itself, but discarding work you already did. An experienced engineer's first move here is to ask: "What do I already know that I'm about to recompute?" The answer is the frequency counts of everything currently in the window. If you keep those counts updated incrementally instead of recalculating them, you can process each element exactly once.
That leads directly to the sliding window pattern: two pointers, left and right, define the window's boundaries. A dictionary tracks how many times each value currently appears inside that window. The right pointer always moves forward, adding one new element at a time. The left pointer only moves forward when the window becomes illegal ā that is, when some value's count exceeds k.
Here's the part that trips people up: shrinking is a while loop, not an if. If duplicates pile up quickly, dropping a single element from the left might not be enough to make the window legal again. You need to keep shrinking until the count is actually back at or below k.
Brute Force Solution
Idea: For every pair of start and end indices, count the frequency of every value in that subarray from scratch, and check if the maximum frequency is at most k.
Algorithm:
- For every possible
startindex. - For every possible
endindex greater than or equal tostart. - Count frequencies of all elements between
startandend. - If the max frequency is at most
k, update the best length.
Advantages: Trivial to reason about and implement; hard to get wrong.
Disadvantages: Recomputes counts that barely changed between adjacent windows. Quadratic number of subarrays, and counting each one costs additional time ā this becomes far too slow once the input grows.
Time complexity: O(n³) in the naive form (O(n²) subarrays à O(n) to count each), or O(n²) if you count incrementally per start. Either way, it doesn't scale.
Space complexity: O(n) for the frequency map used per subarray check.
Optimal Solution
The optimal solution uses a growing-and-shrinking window with a single frequency dictionary.
- Start with an empty
countsdictionary,left = 0, andbest = 0. - For every
rightposition, add the value atrightintocounts, incrementing its count. - While the count of that value exceeds
k, remove the value atleftfromcountsand moveleftforward ā repeat until the window is legal again. - After each shrink step, the window is guaranteed legal. Compute its size as
right - left + 1, and updatebestif it's larger.
| Step | right | Value added | Window | Legal? | Best so far |
|---|---|---|---|---|---|
| 1 | 0 | 1 | [1] | ā | 1 |
| 2 | 1 | 2 | [1,2] | ā | 2 |
| 3 | 2 | 3 | [1,2,3] | ā | 3 |
| 4 | 3 | 1 | [1,2,3,1] | ā | 4 |
| 5 | 4 | 2 | [1,2,3,1,2] | ā | 5 |
| 6 | 5 | 3 | [1,2,3,1,2,3] | ā | 6 |
| 7 | 6 | 1 | count of 1 = 3 ā shrink | shrink until legal | 6 |
Every element enters the window exactly once (via right) and leaves at most once (via left). No element is ever recounted from scratch ā that's what turns this into a single linear pass.
Python Code
from collections import defaultdict
def max_subarray_length(nums: list[int], k: int) -> int:
counts = defaultdict(int)
left = 0
best = 0
for right, value in enumerate(nums):
counts[value] += 1
while counts[value] > k:
counts[nums[left]] -= 1
left += 1
best = max(best, right - left + 1)
return best
Code Walkthrough
counts = defaultdict(int): tracks how many times each value currently sits inside the window.defaultdictavoids manual key-existence checks.left = 0: the window's left boundary; it only ever moves forward.for right, value in enumerate(nums):rightis both the loop index and the window's right boundary.counts[value] += 1: the new element enters the window, so its count goes up.while counts[value] > k: not anifā because a single shrink step might not be enough to fix the violation if the window had prior duplicates near the left edge.counts[nums[left]] -= 1; left += 1: remove the leftmost element from the count map, then advanceleft.best = max(best, right - left + 1): once the window is legal, record its size if it's the largest seen so far.
Dry Run
Array: [1, 2, 1, 2, 1, 2, 1, 2], k = 1
| right | value | counts after add | shrink? | left after | window size | best |
|---|---|---|---|---|---|---|
| 0 | 1 | {1:1} | no | 0 | 1 | 1 |
| 1 | 2 | {1:1, 2:1} | no | 0 | 2 | 2 |
| 2 | 1 | {1:2, 2:1} | yes ā drop nums[0]=1 | 1 | 2 | 2 |
| 3 | 2 | {1:1, 2:2} | yes ā drop nums[1]=2 | 2 | 2 | 2 |
| 4 | 1 | {1:2, 2:1} | yes ā drop nums[2]=1 | 3 | 2 | 2 |
| 5 | 2 | {1:1, 2:2} | yes ā drop nums[3]=2 | 4 | 2 | 2 |
| 6 | 1 | {1:2, 2:1} | yes ā drop nums[4]=1 | 5 | 2 | 2 |
| 7 | 2 | {1:1, 2:2} | yes ā drop nums[5]=2 | 6 | 2 | 2 |
Final answer: 2. With k = 1, the window can never grow past two elements once alternating duplicates start appearing.
Edge case: [5, 5, 5, 5, 5, 5, 5], k = 4. The window grows until counts[5] hits 5, at which point it shrinks back down to 4. It can never exceed that again ā answer is 4.
Complexity Analysis
Time: O(n). Each index is added to counts exactly once via right, and removed at most once via left. Since both pointers only move forward and never reset, the total work across the entire pass is linear, even though the inner while loop looks like it could be expensive.
Space: O(n) in the worst case, if every element in the array is distinct and all end up as keys in counts.
These bounds are correct because the two pointers never move backward ā there's no scenario where an element is reprocessed, which is what keeps this from degrading into the brute-force quadratic behavior.
Alternative Solutions
You could theoretically use a Counter from collections with a sorted structure to track the maximum frequency at each step, but it's unnecessary ā you never need to know which value has the highest count, only whether the value you just added crossed the threshold. Adding a heap or sorted container here is over-engineering: it adds O(log n) factors for information the algorithm doesn't need. The plain dictionary approach is both simpler and strictly faster.
Edge Cases
- Empty array: returns
0ā the loop never executes, andbeststays at its initial value. - All identical values with
klarger than the array length: the whole array is the answer, since the window never needs to shrink. k = 0: every window becomes illegal the moment any value is added twice; ifkis0and any value must appear, behavior depends on whether the problem guaranteesk >= 1ā always check constraints before assuming this can't happen.- Single-element array: returns
1regardless ofk, as long ask >= 1. - All unique values: the entire array is always legal, so the answer equals the array's length.
Common Mistakes
- Using
ifinstead ofwhilewhen shrinking. A single removal might not be enough to make the window legal again. - Forgetting to decrement the count when the left pointer moves. This leaves stale counts and produces wrong shrink decisions later.
- Recomputing the window's frequency from scratch on every step, which silently reintroduces the brute-force quadratic behavior.
- Using a
setinstead of a dictionary. A set can tell you a value is present, but not how many times ā and the shrink condition depends entirely on the exact count. - Updating
bestbefore the window is confirmed legal, which can record an invalid window size.
Interview Questions
- How would you modify this to return the actual subarray, not just its length?
- What changes if the constraint were "at least K" instead of "at most K"?
- Can you solve this with a fixed-size window instead of a variable one? Why or why not?
- How would this scale if the array were a live stream instead of a fixed array?
- What's the time complexity if
kwere allowed to be different for each distinct value?
Similar Problems
- Longest Substring Without Repeating Characters (LeetCode 3): same sliding window and count-map pattern, with
k = 1. - Fruit Into Baskets (LeetCode 904): longest subarray with at most 2 distinct values ā a variant constraint on the same window.
- Max Consecutive Ones III (LeetCode 1004): longest window under a different kind of running constraint (flips allowed).
- Subarrays with K Different Integers (LeetCode 992): uses the same window mechanics to count subarrays rather than find the longest one.
Key Takeaways
Whenever a problem asks for the longest or shortest stretch that stays legal under some running rule, reach for two pointers plus a count map before anything more complex. Grow the window from the right, shrink it from the left only when necessary, and let the dictionary tell you instantly when the rule has been broken. That single pattern ā no recounting, no restarting ā collapses what looks like a quadratic problem into a clean linear pass.
Watch the Video
For the full walkthrough with diagrams, live dry runs, and the quiz that tests whether the pattern has actually clicked, watch the video here: {LINK}
About the Series
This breakdown is part of Fun with Learning Technology, a series focused on building real intuition behind coding interview patterns ā not just memorizing solutions, but understanding why they work so the pattern transfers to problems you've never seen before.
Call To Action
If this helped the pattern click, consider watching the full video on YouTube and subscribing ā it helps more than you'd think for a small channel. Subscribe to the Substack for written breakdowns like this one, drop a comment if something felt unclear or you want a specific problem covered next, and share it with anyone prepping for interviews.
The solution
def maxSubarrayLength(nums, k):
counts = {}
left = 0
best = 0
for right, val in enumerate(nums):
counts[val] = counts.get(val, 0) + 1
while counts[val] > k:
left_val = nums[left]
counts[left_val] -= 1
left += 1
best = max(best, right - left + 1)
return bestReady to try it yourself? Solve Hashmap problems with instant feedback in the practice sandbox.
Practice now ā


