1224. Maximum Equal Frequency
Problem Description
You are given an array nums
containing positive integers. Your task is to find the longest possible prefix of this array where you can remove exactly one element, and after removal, all remaining numbers have the same frequency (number of occurrences).
For example, if you have a prefix where some numbers appear 3 times and others appear 2 times, you need to check if removing one element can make all numbers have the same frequency - either all appearing 2 times or all appearing 3 times.
Key points to understand:
- You must remove exactly one element from the prefix
- After removal, every distinct number that remains must appear the same number of times
- If removing one element leaves the prefix empty, this is valid (all numbers have 0 occurrences)
- You want the longest such prefix
The solution tracks two important pieces of information:
cnt
: How many times each number appears in the current prefixccnt
: How many numbers have a specific count (e.g., how many numbers appear exactly 2 times)
The algorithm checks three main conditions for a valid prefix of length i
:
- All numbers appear once (
mx == 1
): Removing any single element leaves all others with 0 occurrences, which satisfies the requirement - Numbers have counts of
mx
andmx-1
, with only one number at countmx
: Removing one occurrence of the number withmx
appearances makes all numbers havemx-1
occurrences - All numbers except one appear
mx
times, and one number appears once: Removing the single occurrence makes all remaining numbers havemx
occurrences
The solution iterates through the array, continuously updating the counts and checking these conditions to find the maximum valid prefix length.
Intuition
The key insight is recognizing that for a prefix to be valid after removing one element, the frequency distribution must follow very specific patterns. Let's think about what makes a removal valid.
When we remove one element from a prefix, we're essentially decreasing the count of that particular number by 1. For all remaining numbers to have equal frequency afterward, we need to start with a frequency distribution that's "almost uniform" - meaning it's just one element away from being uniform.
What patterns allow this? Let's think through the possibilities:
-
Everything appears once: If every number appears exactly once, removing any element leaves an empty occurrence for that number, and all others remain at 0 occurrences. This is trivially valid.
-
Two frequency levels differing by 1: Imagine we have some numbers appearing
k
times and others appearingk-1
times. If exactly one number appearsk
times, we can remove one instance of it to bring it down tok-1
, matching all others. -
One outlier with frequency 1: If all numbers except one appear
k
times, and that outlier appears just once, we can remove that single occurrence entirely, leaving all remaining numbers at frequencyk
.
The clever part is realizing we don't need to track the entire frequency distribution in detail - we just need to know:
- The maximum frequency (
mx
) - How many numbers have each frequency (
ccnt
) - The total count of elements seen so far
By maintaining these counters as we traverse the array, we can check at each position whether the current prefix satisfies one of our valid patterns. The mathematical conditions in the code directly correspond to these three scenarios:
mx == 1
checks pattern 1ccnt[mx] * mx + ccnt[mx - 1] * (mx - 1) == i and ccnt[mx] == 1
checks pattern 2ccnt[mx] * mx + 1 == i and ccnt[1] == 1
checks pattern 3
This approach is efficient because we're not actually trying different removals - we're mathematically determining if the current frequency distribution allows for a valid removal.
Solution Approach
We use two hash tables (Counters in Python) to track the frequency information:
cnt
: Maps each element value to its frequency in the current prefixccnt
: Maps each frequency to how many elements have that frequencymx
: Tracks the maximum frequency among all elements
Let's walk through the implementation step by step:
1. Initialize data structures:
cnt = Counter() # element -> frequency ccnt = Counter() # frequency -> count of elements with that frequency ans = mx = 0 # ans: result, mx: max frequency
2. Process each element with 1-based indexing:
For each element v
at position i
(1-indexed):
3. Update the frequency counters:
if v in cnt:
ccnt[cnt[v]] -= 1 # Decrease count for old frequency
cnt[v] += 1 # Increment frequency of v
mx = max(mx, cnt[v]) # Update maximum frequency
ccnt[cnt[v]] += 1 # Increase count for new frequency
This maintains the invariant that ccnt[k]
tells us how many distinct elements appear exactly k
times.
4. Check the three valid conditions:
Condition 1: All elements appear once
if mx == 1: ans = i
When every element appears exactly once, removing any element results in uniform frequencies (0 for the removed element, 1 for others becomes 0 after removal).
Condition 2: Two frequency levels (mx and mx-1) with one element at mx
elif ccnt[mx] * mx + ccnt[mx - 1] * (mx - 1) == i and ccnt[mx] == 1: ans = i
ccnt[mx] * mx + ccnt[mx - 1] * (mx - 1) == i
verifies that all elements have frequencies of eithermx
ormx-1
ccnt[mx] == 1
ensures only one element has the maximum frequency- We can remove one occurrence of the element with frequency
mx
to make all frequencies equal tomx-1
Condition 3: All elements at frequency mx except one outlier at frequency 1
elif ccnt[mx] * mx + 1 == i and ccnt[1] == 1: ans = i
ccnt[mx] * mx + 1 == i
checks that the total count equals all elements at frequencymx
plus one extraccnt[1] == 1
confirms exactly one element appears once- We can remove the single-occurrence element entirely
5. Return the maximum valid prefix length:
The algorithm continuously updates ans
whenever a valid prefix is found, ensuring we get the longest possible valid prefix.
The time complexity is O(n)
where n
is the length of the array, as we process each element once. The space complexity is O(n)
for the hash tables in the worst case where all elements are unique.
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 solution with the array nums = [2, 2, 1, 1, 5, 3, 3, 5]
.
We'll track cnt
(element frequencies), ccnt
(frequency counts), and check our three conditions at each step.
Initial state: cnt = {}
, ccnt = {}
, mx = 0
, ans = 0
i = 1, element = 2:
- Update:
cnt = {2: 1}
,ccnt = {1: 1}
,mx = 1
- Check conditions:
mx == 1
✓ (all elements appear once)
ans = 1
i = 2, element = 2:
- Update:
cnt = {2: 2}
,ccnt = {1: 0, 2: 1}
,mx = 2
- Check conditions:
mx == 1
✗ccnt[2] * 2 + ccnt[1] * 1 = 1*2 + 0*1 = 2 == i
✓ andccnt[2] == 1
✓
ans = 2
(we can remove one '2' to make all frequencies equal to 1)
i = 3, element = 1:
- Update:
cnt = {2: 2, 1: 1}
,ccnt = {1: 1, 2: 1}
,mx = 2
- Check conditions:
mx == 1
✗ccnt[2] * 2 + ccnt[1] * 1 = 1*2 + 1*1 = 3 == i
✓ andccnt[2] == 1
✓
ans = 3
(remove one '2' to get all frequencies equal to 1)
i = 4, element = 1:
- Update:
cnt = {2: 2, 1: 2}
,ccnt = {1: 0, 2: 2}
,mx = 2
- Check conditions:
mx == 1
✗ccnt[2] * 2 + ccnt[1] * 1 = 2*2 + 0*1 = 4 == i
✓ butccnt[2] == 2
✗ccnt[2] * 2 + 1 = 2*2 + 1 = 5 ≠ 4
✗
- No update to
ans
i = 5, element = 5:
- Update:
cnt = {2: 2, 1: 2, 5: 1}
,ccnt = {1: 1, 2: 2}
,mx = 2
- Check conditions:
mx == 1
✗ccnt[2] * 2 + ccnt[1] * 1 = 2*2 + 1*1 = 5 == i
✓ butccnt[2] == 2
✗ccnt[2] * 2 + 1 = 2*2 + 1 = 5 == i
✓ andccnt[1] == 1
✓
ans = 5
(remove the single '5' to get all frequencies equal to 2)
i = 6, element = 3:
- Update:
cnt = {2: 2, 1: 2, 5: 1, 3: 1}
,ccnt = {1: 2, 2: 2}
,mx = 2
- Check conditions:
mx == 1
✗ccnt[2] * 2 + ccnt[1] * 1 = 2*2 + 2*1 = 6 == i
✓ butccnt[2] == 2
✗ccnt[2] * 2 + 1 = 2*2 + 1 = 5 ≠ 6
✗
- No update to
ans
i = 7, element = 3:
- Update:
cnt = {2: 2, 1: 2, 5: 1, 3: 2}
,ccnt = {1: 1, 2: 3}
,mx = 2
- Check conditions:
mx == 1
✗ccnt[2] * 2 + ccnt[1] * 1 = 3*2 + 1*1 = 7 == i
✓ butccnt[2] == 3
✗ccnt[2] * 2 + 1 = 3*2 + 1 = 7 == i
✓ andccnt[1] == 1
✓
ans = 7
(remove the single '5' to get all frequencies equal to 2)
i = 8, element = 5:
- Update:
cnt = {2: 2, 1: 2, 5: 2, 3: 2}
,ccnt = {1: 0, 2: 4}
,mx = 2
- Check conditions: None satisfied
- Final
ans = 7
The longest valid prefix has length 7, where we can remove the element '5' (which appears once) to make all remaining elements appear exactly 2 times.
Solution Implementation
1class Solution:
2 def maxEqualFreq(self, nums: List[int]) -> int:
3 # freq_count: maps each number to its frequency
4 freq_count = {}
5
6 # freq_freq_count: maps each frequency to how many numbers have that frequency
7 freq_freq_count = {}
8
9 max_length = 0
10 max_frequency = 0
11
12 for index, num in enumerate(nums, 1):
13 # If number already exists, update frequency of frequencies
14 if num in freq_count:
15 old_freq = freq_count[num]
16 freq_freq_count[old_freq] -= 1
17 if freq_freq_count[old_freq] == 0:
18 del freq_freq_count[old_freq]
19
20 # Increment frequency of current number
21 freq_count[num] = freq_count.get(num, 0) + 1
22 new_freq = freq_count[num]
23
24 # Update maximum frequency seen
25 max_frequency = max(max_frequency, new_freq)
26
27 # Update frequency of frequencies
28 freq_freq_count[new_freq] = freq_freq_count.get(new_freq, 0) + 1
29
30 # Check if current prefix is valid for removal of one element
31
32 # Case 1: All elements appear exactly once (can remove any)
33 if max_frequency == 1:
34 max_length = index
35
36 # Case 2: All elements have frequency max_frequency except one has (max_frequency - 1)
37 # Remove one occurrence from the element with max_frequency
38 elif (freq_freq_count.get(max_frequency, 0) * max_frequency +
39 freq_freq_count.get(max_frequency - 1, 0) * (max_frequency - 1) == index and
40 freq_freq_count.get(max_frequency, 0) == 1):
41 max_length = index
42
43 # Case 3: All elements have frequency max_frequency except one element appears once
44 # Remove the element that appears once
45 elif (freq_freq_count.get(max_frequency, 0) * max_frequency + 1 == index and
46 freq_freq_count.get(1, 0) == 1):
47 max_length = index
48
49 # Case 4: All elements have same frequency f, and one has frequency f+1
50 # This is handled by Case 2 when max_frequency = f+1
51
52 # Case 5: Only one unique number (all same)
53 elif len(freq_count) == 1:
54 max_length = index
55
56 # Case 6: All have same frequency and total elements = (frequency + 1) * unique_count
57 # Remove one from any element
58 elif (len(freq_freq_count) == 1 and
59 (max_frequency + 1) * freq_freq_count.get(max_frequency, 0) == index):
60 max_length = index
61
62 return max_length
63
1class Solution {
2 // Array to store frequency of each number (element value -> its frequency)
3 private static int[] frequencyCount = new int[100010];
4 // Array to store count of frequencies (frequency value -> how many elements have this frequency)
5 private static int[] frequencyOfFrequencies = new int[100010];
6
7 public int maxEqualFreq(int[] nums) {
8 // Initialize arrays to zero
9 Arrays.fill(frequencyCount, 0);
10 Arrays.fill(frequencyOfFrequencies, 0);
11
12 int maxPrefixLength = 0;
13 int maxFrequency = 0; // Track the maximum frequency among all elements
14
15 // Iterate through the array, considering prefixes of length 1 to n
16 for (int prefixLen = 1; prefixLen <= nums.length; ++prefixLen) {
17 int currentNum = nums[prefixLen - 1];
18
19 // If current number already appeared, update frequency count
20 if (frequencyCount[currentNum] > 0) {
21 // Decrease count for the old frequency
22 --frequencyOfFrequencies[frequencyCount[currentNum]];
23 }
24
25 // Increment frequency of current number
26 ++frequencyCount[currentNum];
27
28 // Update maximum frequency
29 maxFrequency = Math.max(maxFrequency, frequencyCount[currentNum]);
30
31 // Increment count for the new frequency
32 ++frequencyOfFrequencies[frequencyCount[currentNum]];
33
34 // Check if current prefix is valid (can remove one element to make all equal)
35
36 // Case 1: All elements appear exactly once (can remove any element)
37 if (maxFrequency == 1) {
38 maxPrefixLength = prefixLen;
39 }
40 // Case 2: All elements have frequency (maxFreq-1) except one with maxFreq
41 // Remove one occurrence from the element with maxFreq
42 else if (frequencyOfFrequencies[maxFrequency] * maxFrequency +
43 frequencyOfFrequencies[maxFrequency - 1] * (maxFrequency - 1) == prefixLen &&
44 frequencyOfFrequencies[maxFrequency] == 1) {
45 maxPrefixLength = prefixLen;
46 }
47 // Case 3: All elements have frequency maxFreq except one with frequency 1
48 // Remove the element with frequency 1
49 else if (frequencyOfFrequencies[maxFrequency] * maxFrequency + 1 == prefixLen &&
50 frequencyOfFrequencies[1] == 1) {
51 maxPrefixLength = prefixLen;
52 }
53 }
54
55 return maxPrefixLength;
56 }
57}
58
1class Solution {
2public:
3 int maxEqualFreq(vector<int>& nums) {
4 // frequency: maps each number to its frequency count
5 unordered_map<int, int> frequency;
6
7 // frequencyCount: maps each frequency value to how many numbers have that frequency
8 unordered_map<int, int> frequencyCount;
9
10 int maxLength = 0;
11 int maxFrequency = 0;
12
13 for (int i = 1; i <= nums.size(); ++i) {
14 int currentNum = nums[i - 1];
15
16 // Update frequency count map before changing the frequency
17 if (frequency[currentNum] > 0) {
18 --frequencyCount[frequency[currentNum]];
19 }
20
21 // Increment frequency of current number
22 ++frequency[currentNum];
23
24 // Track the maximum frequency seen so far
25 maxFrequency = max(maxFrequency, frequency[currentNum]);
26
27 // Update frequency count map with new frequency
28 ++frequencyCount[frequency[currentNum]];
29
30 // Check if current prefix satisfies the condition
31 // Case 1: All elements appear exactly once
32 if (maxFrequency == 1) {
33 maxLength = i;
34 }
35 // Case 2: All elements have frequency (maxFrequency - 1) except one element with frequency maxFrequency
36 // We can remove one occurrence from the element with maxFrequency
37 else if (frequencyCount[maxFrequency] * maxFrequency +
38 frequencyCount[maxFrequency - 1] * (maxFrequency - 1) == i &&
39 frequencyCount[maxFrequency] == 1) {
40 maxLength = i;
41 }
42 // Case 3: All elements have frequency maxFrequency except one element with frequency 1
43 // We can remove the element with frequency 1
44 else if (frequencyCount[maxFrequency] * maxFrequency + 1 == i &&
45 frequencyCount[1] == 1) {
46 maxLength = i;
47 }
48 }
49
50 return maxLength;
51 }
52};
53
1/**
2 * Finds the maximum length prefix of nums where we can remove exactly one element
3 * to make all remaining elements have the same frequency
4 * @param nums - Array of numbers to process
5 * @returns Maximum valid prefix length
6 */
7function maxEqualFreq(nums: number[]): number {
8 const arrayLength = nums.length;
9
10 // Count frequency of each number in the entire array
11 const frequencyMap = new Map<number, number>();
12 for (const num of nums) {
13 frequencyMap.set(num, (frequencyMap.get(num) ?? 0) + 1);
14 }
15
16 // Try each prefix length from longest to shortest
17 for (let prefixLength = arrayLength - 1; prefixLength > 0; prefixLength--) {
18 // Try removing one occurrence of each unique number
19 for (const numberToReduce of frequencyMap.keys()) {
20 // Temporarily reduce frequency of current number by 1
21 frequencyMap.set(numberToReduce, frequencyMap.get(numberToReduce)! - 1);
22
23 // Find the first non-zero frequency as reference
24 let referenceFrequency = 0;
25 for (const frequency of frequencyMap.values()) {
26 if (frequency !== 0) {
27 referenceFrequency = frequency;
28 break;
29 }
30 }
31
32 // Check if all non-zero frequencies are equal
33 let allFrequenciesEqual = true;
34 let totalElements = 1; // Start with 1 for the removed element
35
36 for (const frequency of frequencyMap.values()) {
37 if (frequency !== 0 && frequency !== referenceFrequency) {
38 allFrequenciesEqual = false;
39 break;
40 }
41 totalElements += frequency;
42 }
43
44 // If valid configuration found, return the total elements count
45 if (allFrequenciesEqual) {
46 return totalElements;
47 }
48
49 // Restore the frequency for next iteration
50 frequencyMap.set(numberToReduce, frequencyMap.get(numberToReduce)! + 1);
51 }
52
53 // Remove the last element of current prefix for next iteration
54 frequencyMap.set(nums[prefixLength], frequencyMap.get(nums[prefixLength])! - 1);
55 }
56
57 // Minimum valid prefix length is 1
58 return 1;
59}
60
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the nums
array. The algorithm iterates through the array once with a single loop. Within each iteration, all operations are constant time: accessing and updating dictionary values (cnt
and ccnt
), arithmetic operations, and comparisons. Even though we use Counter
objects and perform dictionary operations, each individual operation takes O(1)
time.
The space complexity is O(n)
. In the worst case, when all elements in nums
are unique, the cnt
dictionary will store n
different keys. The ccnt
dictionary stores the frequency of frequencies, which is bounded by O(n)
as well (at most n
different frequency values). The variables ans
, mx
, and i
use constant space. Therefore, the overall space complexity is O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Missing Edge Cases in Validation Conditions
One of the most common pitfalls is not covering all valid scenarios where removing one element creates uniform frequencies. The code above actually has redundant conditions (Cases 5 and 6) that attempt to patch missing logic, making it harder to understand and potentially buggy.
The Problem: The three main conditions might miss valid prefixes like:
- When all elements have the same frequency
f
, and removing one element from any of them creates uniform frequencyf-1
- When there's only one unique element in the prefix
The Fix: Consolidate the validation logic into clear, comprehensive conditions:
def maxEqualFreq(self, nums: List[int]) -> int:
cnt = {} # element -> frequency
ccnt = {} # frequency -> count of elements with that frequency
ans = mx = 0
for i, v in enumerate(nums, 1):
# Update frequencies
if v in cnt:
ccnt[cnt[v]] -= 1
if ccnt[cnt[v]] == 0:
del ccnt[cnt[v]]
cnt[v] = cnt.get(v, 0) + 1
mx = max(mx, cnt[v])
ccnt[cnt[v]] = ccnt.get(cnt[v], 0) + 1
# Check valid conditions
# Case 1: All elements appear once
if mx == 1:
ans = i
# Case 2: Only one unique element (can remove one occurrence)
elif len(cnt) == 1:
ans = i
# Case 3: Two frequency levels (mx and mx-1) with one element at mx
elif (len(ccnt) == 2 and
mx - 1 in ccnt and
(ccnt[mx] == 1 and ccnt[mx] * mx + ccnt[mx - 1] * (mx - 1) == i)):
ans = i
# Case 4: All at frequency mx except one element appears once
elif (len(ccnt) == 2 and
1 in ccnt and
ccnt[1] == 1 and
ccnt[mx] * mx + 1 == i):
ans = i
# Case 5: All have same frequency, removing one creates uniform freq
elif len(ccnt) == 1 and (mx * len(cnt) == i + 1):
ans = i
return ans
2. Incorrect Frequency Counter Management
The Problem:
Forgetting to remove entries from ccnt
when their count becomes zero leads to incorrect validation checks. The dictionary might contain keys with value 0, causing conditions like mx - 1 in ccnt
to incorrectly return True.
The Fix: Always clean up zero-count entries:
if v in cnt: ccnt[cnt[v]] -= 1 if ccnt[cnt[v]] == 0: del ccnt[cnt[v]] # Critical: remove zero entries
3. Off-by-One Errors with Indexing
The Problem: Mixing 0-based and 1-based indexing when checking if the total count equals the prefix length. The enumerate starts at 1, but forgetting this can lead to incorrect validation.
The Fix: Be consistent with 1-based indexing throughout:
for i, v in enumerate(nums, 1): # i is 1-based
# All checks use i as the current prefix length
if ccnt[mx] * mx + ccnt[mx - 1] * (mx - 1) == i: # Correct
4. Not Handling the "All Same Frequency" Case Properly
The Problem:
When all elements have the same frequency f
, we can remove one occurrence from any element to achieve uniform frequency f-1
. This case is often missed or incorrectly implemented.
The Fix: Add explicit check for this scenario:
# All elements have same frequency f
# Total = f * unique_count
# After removing one: (f-1) * unique_count + (unique_count - 1)
elif len(ccnt) == 1 and mx * len(cnt) == i + 1:
ans = i
This ensures we catch prefixes where all elements appear the same number of times and removing one element from any of them creates a valid uniform distribution.
Which algorithm should you use to find a node that is close to the root of the tree?
Recommended Readings
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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!