2831. Find the Longest Equal Subarray
Problem Description
You are given a 0-indexed integer array nums
and an integer k
.
The problem asks you to find the length of the longest possible equal subarray that can be formed after deleting at most k
elements from nums
.
An equal subarray is defined as a subarray where all elements have the same value. The empty subarray is also considered an equal subarray.
A subarray is a contiguous sequence of elements within the array (can be empty).
Key Points:
- You can delete at most
k
elements from the array - After deletion, you want to find the longest contiguous sequence where all elements are equal
- The elements you delete don't have to be contiguous - you can delete any elements as long as the remaining elements form a contiguous equal subarray
Example Understanding:
If you have an array like [1, 3, 1, 2, 1]
and k = 1
, you could delete the element 3
or 2
to get subarrays like [1, 1, 2, 1]
or [1, 3, 1, 1]
. The longest equal subarray you can achieve would be of length 3 (three 1's) by deleting either the 3
or the 2
.
The solution uses a sliding window approach with a hash table to track element frequencies. The window maintains the property that the number of elements that need to be deleted (total elements in window minus the most frequent element count) doesn't exceed k
.
Intuition
The key insight is to reframe the problem: instead of thinking about "deleting elements to form an equal subarray," we can think about it as "finding a window where one element appears frequently enough that we can delete all other elements within our budget of k
deletions."
Let's think about what makes a valid equal subarray after deletions. If we have a window of size w
and the most frequent element in that window appears mx
times, then we need to delete w - mx
elements to make all elements equal. This deletion count must be at most k
.
This leads us to the sliding window approach: we can maintain a window and track the frequency of each element. As we expand the window by moving the right pointer, we keep track of the maximum frequency mx
of any element in the current window.
The crucial observation is that we want to maximize the count of the most frequent element while keeping the number of deletions within budget. If a window has size r - l + 1
and the most frequent element appears mx
times, then:
- Number of deletions needed =
(r - l + 1) - mx
- This must satisfy:
(r - l + 1) - mx ≤ k
When the deletion count exceeds k
, we shrink the window from the left. The beauty of this approach is that we don't need to find which specific elements to delete - we just need to ensure that the window always maintains the property that it can be converted to an equal subarray with at most k
deletions.
The answer is simply the maximum frequency mx
we've seen across all valid windows, because that represents the longest equal subarray we can achieve after performing the allowed deletions.
Learn more about Binary Search and Sliding Window patterns.
Solution Approach
The solution uses a sliding window technique with a hash table to efficiently find the longest equal subarray.
Data Structures Used:
cnt
: A Counter (hash table) to track the frequency of each element in the current windowl
andr
: Two pointers representing the left and right boundaries of the sliding windowmx
: Variable to track the maximum frequency of any element seen so far
Algorithm Steps:
-
Initialize variables:
cnt = Counter()
- empty hash table for element frequenciesl = 0
- left pointer of the windowmx = 0
- maximum frequency tracker
-
Expand window with right pointer:
- Iterate through the array using
enumerate(nums)
wherer
is the index andx
is the element - For each element
x
at positionr
:- Add it to the window:
cnt[x] += 1
- Update maximum frequency:
mx = max(mx, cnt[x])
- Add it to the window:
- Iterate through the array using
-
Check and shrink window if needed:
- Calculate current window size:
r - l + 1
- Calculate deletions needed:
r - l + 1 - mx
- If deletions needed exceed
k
:- Remove the leftmost element from the window:
cnt[nums[l]] -= 1
- Move left pointer:
l += 1
- This maintains the invariant that the window can always form an equal subarray with at most
k
deletions
- Remove the leftmost element from the window:
- Calculate current window size:
-
Return the result:
- After processing all elements,
mx
contains the length of the longest equal subarray achievable
- After processing all elements,
Key Insight:
The algorithm maintains a monotonically variable length window where the number of elements that need to be deleted never exceeds k
. By tracking the maximum frequency across all valid windows, we find the optimal solution without explicitly determining which elements to delete.
Time Complexity: O(n)
where n
is the length of the array, as each element is visited at most twice (once by right pointer, once by left pointer)
Space Complexity: O(m)
where m
is the number of unique elements in the array, for storing the frequency map
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the algorithm with nums = [1, 3, 2, 3, 1, 3]
and k = 2
.
We want to find the longest equal subarray after deleting at most 2 elements.
Initial State:
l = 0
,mx = 0
cnt = {}
- Window: empty
Step 1: r = 0, x = 1
- Add 1 to window:
cnt = {1: 1}
- Update max frequency:
mx = max(0, 1) = 1
- Window
[1]
, size = 1 - Deletions needed:
1 - 1 = 0 ≤ 2
✓ - Keep window
Step 2: r = 1, x = 3
- Add 3 to window:
cnt = {1: 1, 3: 1}
- Update max frequency:
mx = max(1, 1) = 1
- Window
[1, 3]
, size = 2 - Deletions needed:
2 - 1 = 1 ≤ 2
✓ - Keep window
Step 3: r = 2, x = 2
- Add 2 to window:
cnt = {1: 1, 3: 1, 2: 1}
- Update max frequency:
mx = max(1, 1) = 1
- Window
[1, 3, 2]
, size = 3 - Deletions needed:
3 - 1 = 2 ≤ 2
✓ - Keep window
Step 4: r = 3, x = 3
- Add 3 to window:
cnt = {1: 1, 3: 2, 2: 1}
- Update max frequency:
mx = max(1, 2) = 2
- Window
[1, 3, 2, 3]
, size = 4 - Deletions needed:
4 - 2 = 2 ≤ 2
✓ - Keep window
Step 5: r = 4, x = 1
- Add 1 to window:
cnt = {1: 2, 3: 2, 2: 1}
- Update max frequency:
mx = max(2, 2) = 2
- Window
[1, 3, 2, 3, 1]
, size = 5 - Deletions needed:
5 - 2 = 3 > 2
✗ - Shrink window: Remove
nums[0] = 1
cnt = {1: 1, 3: 2, 2: 1}
l = 1
- New window
[3, 2, 3, 1]
, size = 4 - Deletions needed:
4 - 2 = 2 ≤ 2
✓
Step 6: r = 5, x = 3
- Add 3 to window:
cnt = {1: 1, 3: 3, 2: 1}
- Update max frequency:
mx = max(2, 3) = 3
- Window
[3, 2, 3, 1, 3]
, size = 5 - Deletions needed:
5 - 3 = 2 ≤ 2
✓ - Keep window
Result: mx = 3
The algorithm found that we can create an equal subarray of length 3 (three 3's) by deleting at most 2 elements (the 2 and the 1 in the window [3, 2, 3, 1, 3]
).
Solution Implementation
1class Solution:
2 def longestEqualSubarray(self, nums: List[int], k: int) -> int:
3 # Dictionary to count frequency of each element in current window
4 frequency_map = {}
5
6 # Left pointer of sliding window
7 left = 0
8
9 # Maximum frequency of any single element seen so far
10 max_frequency = 0
11
12 # Iterate through array with right pointer
13 for right in range(len(nums)):
14 current_element = nums[right]
15
16 # Update frequency of current element
17 frequency_map[current_element] = frequency_map.get(current_element, 0) + 1
18
19 # Update maximum frequency if current element's frequency is higher
20 max_frequency = max(max_frequency, frequency_map[current_element])
21
22 # Calculate window size and elements to remove
23 window_size = right - left + 1
24 elements_to_remove = window_size - max_frequency
25
26 # If we need to remove more than k elements, shrink window from left
27 if elements_to_remove > k:
28 left_element = nums[left]
29 frequency_map[left_element] -= 1
30 left += 1
31
32 # Return the maximum frequency (longest equal subarray after removals)
33 return max_frequency
34
1class Solution {
2 public int longestEqualSubarray(List<Integer> nums, int k) {
3 // Map to store frequency count of each number in current window
4 Map<Integer, Integer> frequencyMap = new HashMap<>();
5
6 // Track the maximum frequency of any single element in current window
7 int maxFrequency = 0;
8
9 // Left pointer of sliding window
10 int left = 0;
11
12 // Iterate through array with right pointer
13 for (int right = 0; right < nums.size(); right++) {
14 // Get current element at right pointer
15 int currentNum = nums.get(right);
16
17 // Increment frequency count of current element
18 frequencyMap.merge(currentNum, 1, Integer::sum);
19
20 // Update maximum frequency if current element's frequency is higher
21 maxFrequency = Math.max(maxFrequency, frequencyMap.get(currentNum));
22
23 // Check if current window needs more than k deletions
24 // Window size - max frequency = elements to delete
25 if (right - left + 1 - maxFrequency > k) {
26 // Shrink window from left
27 int leftNum = nums.get(left);
28 frequencyMap.merge(leftNum, -1, Integer::sum);
29 left++;
30 }
31 }
32
33 // Return the maximum frequency found (length of longest equal subarray)
34 return maxFrequency;
35 }
36}
37
1class Solution {
2public:
3 int longestEqualSubarray(vector<int>& nums, int k) {
4 // Map to track frequency of each element in current window
5 unordered_map<int, int> elementFrequency;
6
7 // Maximum frequency of any single element in current window
8 int maxFrequency = 0;
9
10 // Left pointer of sliding window
11 int left = 0;
12
13 // Iterate through array with right pointer
14 for (int right = 0; right < nums.size(); ++right) {
15 // Add current element to window and update its frequency
16 int currentElement = nums[right];
17 elementFrequency[currentElement]++;
18
19 // Update maximum frequency if current element's frequency is higher
20 maxFrequency = max(maxFrequency, elementFrequency[currentElement]);
21
22 // Current window size
23 int windowSize = right - left + 1;
24
25 // Elements to delete = window size - most frequent element count
26 int elementsToDelete = windowSize - maxFrequency;
27
28 // If we need to delete more than k elements, shrink window from left
29 if (elementsToDelete > k) {
30 // Remove leftmost element from window
31 int leftElement = nums[left];
32 elementFrequency[leftElement]--;
33 left++;
34 }
35 }
36
37 // Return the maximum frequency found (length of longest equal subarray)
38 return maxFrequency;
39 }
40};
41
1/**
2 * Finds the length of the longest subarray where all elements are equal
3 * after removing at most k elements from the subarray.
4 *
5 * @param nums - The input array of numbers
6 * @param k - Maximum number of elements that can be removed
7 * @returns The maximum length of equal-element subarray after removals
8 */
9function longestEqualSubarray(nums: number[], k: number): number {
10 // Map to store frequency count of each number in current window
11 const frequencyMap: Map<number, number> = new Map();
12
13 // Maximum frequency of any single element in current window
14 let maxFrequency = 0;
15
16 // Left pointer of sliding window
17 let leftPointer = 0;
18
19 // Iterate through array with right pointer
20 for (let rightPointer = 0; rightPointer < nums.length; rightPointer++) {
21 // Increment frequency count for current element
22 const currentElement = nums[rightPointer];
23 const currentFrequency = (frequencyMap.get(currentElement) ?? 0) + 1;
24 frequencyMap.set(currentElement, currentFrequency);
25
26 // Update maximum frequency if current element's frequency is higher
27 maxFrequency = Math.max(maxFrequency, currentFrequency);
28
29 // Current window size
30 const windowSize = rightPointer - leftPointer + 1;
31
32 // If elements to remove exceed k, shrink window from left
33 // (windowSize - maxFrequency) represents elements that need to be removed
34 if (windowSize - maxFrequency > k) {
35 // Decrease frequency count of leftmost element
36 const leftElement = nums[leftPointer];
37 frequencyMap.set(leftElement, frequencyMap.get(leftElement)! - 1);
38
39 // Move left pointer forward
40 leftPointer++;
41 }
42 }
43
44 // Return the maximum frequency found (longest equal subarray length)
45 return maxFrequency;
46}
47
Time and Space Complexity
Time Complexity: O(n)
The algorithm uses a sliding window approach with two pointers (l
and r
). The right pointer r
iterates through the entire array once in the for loop, visiting each element exactly once. The left pointer l
can only move forward and will move at most n
times throughout the entire execution. Each operation inside the loop (counter updates, max comparison, arithmetic operations) takes O(1)
time. Therefore, the overall time complexity is O(n)
, where n
is the length of the input array nums
.
Space Complexity: O(n)
The space complexity is determined by the Counter
object cnt
, which stores the frequency of elements within the current window. In the worst case, when all elements in the array are distinct and the window spans the entire array, the counter would need to store n
different elements. Therefore, the space complexity is O(n)
, where n
is the length of the input array.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrectly Updating Maximum Frequency After Shrinking Window
The Pitfall:
A common mistake is trying to recalculate or decrement the max_frequency
when shrinking the window (moving the left pointer). Some might think that if we remove an element from the window, we should update max_frequency
if that element had the maximum frequency.
Why This is Wrong:
- The algorithm relies on the fact that
max_frequency
represents the historical maximum, not necessarily the current window's maximum frequency - When we shrink the window, we only do so when
window_size - max_frequency > k
- Even if the removed element reduces a frequency count, the window is still invalid with the current
max_frequency
, so we keep shrinking - The algorithm naturally finds the correct answer because we're looking for the maximum possible equal subarray across all windows
Incorrect Implementation Example:
# WRONG - Don't do this!
if elements_to_remove > k:
left_element = nums[left]
frequency_map[left_element] -= 1
# Trying to update max_frequency after removal - THIS IS WRONG
if frequency_map[left_element] < max_frequency:
max_frequency = max(frequency_map.values()) # Expensive and incorrect
left += 1
Correct Approach:
Keep max_frequency
as is - it represents the best we've seen so far and helps maintain the optimal window size.
2. Using Counter Instead of Regular Dictionary
The Pitfall:
While using Counter
from collections seems natural for frequency counting, it can lead to subtle issues:
from collections import Counter # This might seem cleaner but has a pitfall cnt = Counter() # ... cnt[nums[left]] -= 1 # Counter keeps zero and negative values, which can cause confusion
The Issue:
- Counter objects don't automatically remove keys when their count reaches 0
- This can lead to unnecessary memory usage and potential confusion when debugging
Better Approach:
Use a regular dictionary with .get()
method or handle zero counts explicitly:
# Clean up zero counts if needed if frequency_map[left_element] == 0: del frequency_map[left_element]
3. Off-by-One Error in Window Size Calculation
The Pitfall: Forgetting to add 1 when calculating window size:
# WRONG window_size = right - left # Missing the +1 # CORRECT window_size = right - left + 1
Why This Matters:
- The window from index
left
toright
inclusive containsright - left + 1
elements - This error would cause the algorithm to incorrectly calculate the number of deletions needed
- It could lead to accepting invalid windows or rejecting valid ones
Example:
If left = 2
and right = 4
, the window contains elements at indices [2, 3, 4], which is 3 elements, not 2.
Which data structure is used to implement priority queue?
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
https assets algo monster cover_photos stack svg Sliding Window Maximum Monotonic Stack We have an array and a sliding window defined by a start index and an end index The sliding window moves from left of the array to right There are always k elements in the window The window
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!