Before attempting this problem, you should be comfortable with:
Sliding Window Technique - Maintaining a dynamic window with two pointers that expands and contracts based on constraints
Hash Maps - Tracking element frequencies within the current window efficiently
Frequency Counting - Incrementing and decrementing counts as elements enter and leave the window
1. Brute Force
Intuition
The most direct approach is to examine every possible subarray and check if it satisfies the frequency constraint. For each starting position, we extend the subarray one element at a time, tracking element frequencies as we go. The moment any element appears more than k times, we stop extending and move to the next starting position.
Algorithm
Initialize res to track the maximum valid subarray length.
For each starting index i, create a frequency map and iterate through all ending indices j.
Increment the count for each element as we expand the window.
If any element's frequency exceeds k, break out of the inner loop.
Otherwise, update res with the current subarray length j - i + 1.
classSolution:defmaxSubarrayLength(self, nums: List[int], k:int)->int:
n, res =len(nums),0for i inrange(n):
count = defaultdict(int)for j inrange(i, n):
count[nums[j]]+=1if count[nums[j]]> k:break
res =max(res, j - i +1)return res
publicclassSolution{publicintmaxSubarrayLength(int[] nums,int k){int n = nums.length, res =0;for(int i =0; i < n; i++){Map<Integer,Integer> count =newHashMap<>();for(int j = i; j < n; j++){
count.put(nums[j], count.getOrDefault(nums[j],0)+1);if(count.get(nums[j])> k){break;}
res =Math.max(res, j - i +1);}}return res;}}
classSolution{public:intmaxSubarrayLength(vector<int>& nums,int k){int n = nums.size(), res =0;for(int i =0; i < n; i++){
unordered_map<int,int> count;for(int j = i; j < n; j++){
count[nums[j]]++;if(count[nums[j]]> k){break;}
res =max(res, j - i +1);}}return res;}};
classSolution{/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/maxSubarrayLength(nums, k){let n = nums.length,
res =0;for(let i =0; i < n; i++){let count =newMap();for(let j = i; j < n; j++){
count.set(nums[j],(count.get(nums[j])||0)+1);if(count.get(nums[j])> k){break;}
res = Math.max(res, j - i +1);}}return res;}}
publicclassSolution{publicintMaxSubarrayLength(int[] nums,int k){int n = nums.Length, res =0;for(int i =0; i < n; i++){Dictionary<int,int> count =newDictionary<int,int>();for(int j = i; j < n; j++){if(!count.ContainsKey(nums[j])) count[nums[j]]=0;
count[nums[j]]++;if(count[nums[j]]> k){break;}
res = Math.Max(res, j - i +1);}}return res;}}
funcmaxSubarrayLength(nums []int, k int)int{
n, res :=len(nums),0for i :=0; i < n; i++{
count :=make(map[int]int)for j := i; j < n; j++{
count[nums[j]]++if count[nums[j]]> k {break}if j-i+1> res {
res = j - i +1}}}return res
}
class Solution {funmaxSubarrayLength(nums: IntArray, k: Int): Int {val n = nums.size
var res =0for(i in0 until n){val count = HashMap<Int, Int>()for(j in i until n){
count[nums[j]]= count.getOrDefault(nums[j],0)+1if(count[nums[j]]!!> k){break}
res =maxOf(res, j - i +1)}}return res
}}
classSolution{funcmaxSubarrayLength(_ nums:[Int],_ k:Int)->Int{let n = nums.count
var res =0for i in0..<n {var count =[Int:Int]()for j in i..<n {
count[nums[j],default:0]+=1if count[nums[j]]!> k {break}
res =max(res, j - i +1)}}return res
}}
Time & Space Complexity
Time complexity: O(n2)
Space complexity: O(n)
2. Sliding Window
Intuition
Instead of restarting from scratch for each position, we can maintain a sliding window that always represents a valid subarray. When adding an element causes a frequency violation, we shrink the window from the left until the constraint is satisfied again. This way, we only process each element twice at most: once when entering and once when leaving the window.
Algorithm
Use two pointers l (left) and r (right) to define the current window.
Maintain a hash map to track the frequency of each element in the window.
For each right pointer position, add the current element to the frequency map.
While the newly added element's frequency exceeds k, shrink the window by incrementing l and decrementing the corresponding frequency.
Update res with the current window size r - l + 1.
classSolution:defmaxSubarrayLength(self, nums: List[int], k:int)->int:
res =0
count = defaultdict(int)
l =0for r inrange(len(nums)):
count[nums[r]]+=1while count[nums[r]]> k:
count[nums[l]]-=1
l +=1
res =max(res, r - l +1)return res
publicclassSolution{publicintmaxSubarrayLength(int[] nums,int k){int res =0;Map<Integer,Integer> count =newHashMap<>();int l =0;for(int r =0; r < nums.length; r++){
count.put(nums[r], count.getOrDefault(nums[r],0)+1);while(count.get(nums[r])> k){
count.put(nums[l], count.get(nums[l])-1);
l++;}
res =Math.max(res, r - l +1);}return res;}}
classSolution{public:intmaxSubarrayLength(vector<int>& nums,int k){int res =0;
unordered_map<int,int> count;int l =0;for(int r =0; r < nums.size(); r++){
count[nums[r]]++;while(count[nums[r]]> k){
count[nums[l]]--;
l++;}
res =max(res, r - l +1);}return res;}};
classSolution{/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/maxSubarrayLength(nums, k){let res =0;let count =newMap();let l =0;for(let r =0; r < nums.length; r++){
count.set(nums[r],(count.get(nums[r])||0)+1);while(count.get(nums[r])> k){
count.set(nums[l], count.get(nums[l])-1);
l++;}
res = Math.max(res, r - l +1);}return res;}}
publicclassSolution{publicintMaxSubarrayLength(int[] nums,int k){int res =0;Dictionary<int,int> count =newDictionary<int,int>();int l =0;for(int r =0; r < nums.Length; r++){if(!count.ContainsKey(nums[r])) count[nums[r]]=0;
count[nums[r]]++;while(count[nums[r]]> k){
count[nums[l]]--;
l++;}
res = Math.Max(res, r - l +1);}return res;}}
funcmaxSubarrayLength(nums []int, k int)int{
res :=0
count :=make(map[int]int)
l :=0for r :=0; r <len(nums); r++{
count[nums[r]]++for count[nums[r]]> k {
count[nums[l]]--
l++}if r-l+1> res {
res = r - l +1}}return res
}
class Solution {funmaxSubarrayLength(nums: IntArray, k: Int): Int {var res =0val count = HashMap<Int, Int>()var l =0for(r in nums.indices){
count[nums[r]]= count.getOrDefault(nums[r],0)+1while(count[nums[r]]!!> k){
count[nums[l]]= count[nums[l]]!!-1
l++}
res =maxOf(res, r - l +1)}return res
}}
classSolution{funcmaxSubarrayLength(_ nums:[Int],_ k:Int)->Int{var res =0var count =[Int:Int]()var l =0for r in0..<nums.count {
count[nums[r],default:0]+=1while count[nums[r]]!> k {
count[nums[l]]!-=1
l +=1}
res =max(res, r - l +1)}return res
}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n)
3. Sliding Window (Optimal)
Intuition
We can optimize further by observing that we only care about finding the maximum window size. Once we find a valid window of size w, we never need a smaller one. So instead of shrinking the window completely when invalid, we just slide it: move both left and right pointers by one. The window size either stays the same or grows, never shrinks. This guarantees we find the maximum valid window.
Algorithm
Track cnt, the count of elements whose frequency exceeds k.
For each right pointer, increment the element's frequency and update cnt if it just exceeded k.
If cnt > 0 (window is invalid), slide the window by moving the left pointer once. Before moving, check if the left element's frequency was above k and decrement cnt accordingly.
The window size never shrinks, so after processing all elements, the answer is len(nums) - l.
classSolution:defmaxSubarrayLength(self, nums: List[int], k:int)->int:
count = defaultdict(int)
l = res =0
cnt =0# count of numbers with freq > kfor r inrange(len(nums)):
count[nums[r]]+=1
cnt += count[nums[r]]> k
if cnt >0:
cnt -= count[nums[l]]> k
count[nums[l]]-=1
l +=1returnlen(nums)- l
publicclassSolution{publicintmaxSubarrayLength(int[] nums,int k){HashMap<Integer,Integer> count =newHashMap<>();int l =0, cnt =0;// count of numbers with freq > kfor(int r =0; r < nums.length; r++){
count.put(nums[r], count.getOrDefault(nums[r],0)+1);if(count.get(nums[r])> k) cnt++;if(cnt >0){if(count.get(nums[l])> k) cnt--;
count.put(nums[l], count.get(nums[l])-1);
l++;}}return nums.length - l;}}
classSolution{public:intmaxSubarrayLength(vector<int>& nums,int k){
unordered_map<int,int> count;int l =0, cnt =0;// count of numbers with freq > kfor(int r =0; r < nums.size(); r++){
count[nums[r]]++;
cnt += count[nums[r]]> k;if(cnt >0){
cnt -= count[nums[l]]> k;
count[nums[l]]--;
l++;}}return nums.size()- l;}};
classSolution{/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/maxSubarrayLength(nums, k){let count =newMap();let l =0,
cnt =0;// count of numbers with freq > kfor(let r =0; r < nums.length; r++){
count.set(nums[r],(count.get(nums[r])||0)+1);if(count.get(nums[r])> k) cnt++;if(cnt >0){if(count.get(nums[l])> k) cnt--;
count.set(nums[l], count.get(nums[l])-1);
l++;}}return nums.length - l;}}
publicclassSolution{publicintMaxSubarrayLength(int[] nums,int k){Dictionary<int,int> count =newDictionary<int,int>();int l =0, cnt =0;// count of numbers with freq > kfor(int r =0; r < nums.Length; r++){if(!count.ContainsKey(nums[r])) count[nums[r]]=0;
count[nums[r]]++;if(count[nums[r]]> k) cnt++;if(cnt >0){if(count[nums[l]]> k) cnt--;
count[nums[l]]--;
l++;}}return nums.Length - l;}}
funcmaxSubarrayLength(nums []int, k int)int{
count :=make(map[int]int)
l, cnt :=0,0// count of numbers with freq > kfor r :=0; r <len(nums); r++{
count[nums[r]]++if count[nums[r]]> k {
cnt++}if cnt >0{if count[nums[l]]> k {
cnt--}
count[nums[l]]--
l++}}returnlen(nums)- l
}
class Solution {funmaxSubarrayLength(nums: IntArray, k: Int): Int {val count = HashMap<Int, Int>()var l =0var cnt =0// count of numbers with freq > kfor(r in nums.indices){
count[nums[r]]= count.getOrDefault(nums[r],0)+1if(count[nums[r]]!!> k) cnt++if(cnt >0){if(count[nums[l]]!!> k) cnt--
count[nums[l]]= count[nums[l]]!!-1
l++}}return nums.size - l
}}
classSolution{funcmaxSubarrayLength(_ nums:[Int],_ k:Int)->Int{var count =[Int:Int]()var l =0var cnt =0// count of numbers with freq > kfor r in0..<nums.count {
count[nums[r],default:0]+=1if count[nums[r]]!> k {
cnt +=1}if cnt >0{if count[nums[l]]!> k {
cnt -=1}
count[nums[l]]!-=1
l +=1}}return nums.count - l
}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(n)
Common Pitfalls
Forgetting to Decrement Frequency When Shrinking the Window
When the left pointer moves, you must decrement the frequency count of the element being removed from the window. Failing to do this causes the frequency map to retain stale counts, leading to incorrect constraint checks and wrong answers.
Using the Wrong Comparison Operator for the Constraint
The problem asks for elements appearing "at most k times," meaning frequency should be <= k. Using < k instead of <= k (or > k for violation checks) will incorrectly shrink the window too early or allow invalid windows.
Not Updating the Result at the Right Time
In sliding window problems, update the maximum length after ensuring the window is valid, not before. Updating before shrinking the window can record an invalid window size, resulting in an answer that exceeds the actual longest valid subarray.