Facebook Pixel

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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 window
  • l and r: Two pointers representing the left and right boundaries of the sliding window
  • mx: Variable to track the maximum frequency of any element seen so far

Algorithm Steps:

  1. Initialize variables:

    • cnt = Counter() - empty hash table for element frequencies
    • l = 0 - left pointer of the window
    • mx = 0 - maximum frequency tracker
  2. Expand window with right pointer:

    • Iterate through the array using enumerate(nums) where r is the index and x is the element
    • For each element x at position r:
      • Add it to the window: cnt[x] += 1
      • Update maximum frequency: mx = max(mx, cnt[x])
  3. 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
  4. Return the result:

    • After processing all elements, mx contains the length of the longest equal subarray achievable

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 Evaluator

Example 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 to right inclusive contains right - 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which data structure is used to implement priority queue?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More