1838. Frequency of the Most Frequent Element
Problem Description
You are given an integer array nums
and an integer k
. The frequency of an element is the number of times it appears in the array.
In one operation, you can select any index in nums
and increment the element at that index by 1
. You can perform at most k
operations total.
Your goal is to find the maximum possible frequency of any element after performing at most k
operations.
For example, if nums = [1, 2, 4]
and k = 5
, you could increment the first element three times (1→2→3→4) and the second element twice (2→3→4), using all 5 operations. This would give you the array [4, 4, 4]
, where the element 4 has a frequency of 3, which is the maximum possible frequency.
The key insight is that you want to make multiple elements equal to maximize frequency, and since you can only increment values, you'll typically want to increase smaller elements to match a larger target value. The challenge is determining which elements to modify and what target value to choose to maximize the frequency while staying within the operation budget k
.
Intuition
Let's think about what happens when we try to maximize frequency. We want to make as many elements equal as possible using our limited operations.
First, consider which value we should target. Since we can only increment elements (never decrement), if we want to make multiple elements equal, we must choose a target value that's at least as large as all the elements we're modifying. The most efficient choice is to pick one of the existing elements as our target - specifically, the largest element in the group we're modifying. Why? Because if we pick any value larger than the maximum element, we're wasting operations by incrementing that maximum element unnecessarily.
Next, which elements should we modify? If we sort the array first, it becomes clear that we should pick consecutive elements. For instance, if we have [1, 2, 5, 8, 10]
and want to make three elements equal to 5, it makes sense to choose [1, 2, 5]
rather than [1, 2, 8]
because the closer the elements are to our target, the fewer operations we need.
Now, how do we find the maximum frequency? We could check every possible window size (frequency), but that would be inefficient. Here's a key observation: if we can achieve a frequency of m
, then we can definitely achieve any frequency less than m
(we just use fewer elements). This monotonic property suggests binary search!
For binary search, we need to check if a given frequency m
is achievable. We slide a window of size m
through the sorted array and for each window, calculate the cost to make all elements equal to the maximum element in that window. If the window ends at index i
, the maximum element is nums[i-1]
, and the cost is nums[i-1] * m - sum(window)
. We use a prefix sum array to quickly calculate the sum of any window.
The binary search finds the largest frequency where at least one window of that size can be transformed within our operation budget k
.
Learn more about Greedy, Binary Search, Prefix Sum, Sorting and Sliding Window patterns.
Solution Approach
Let's implement the solution step by step based on our intuition:
Step 1: Sort the array and compute prefix sums
First, we sort the array to group similar elements together. This allows us to work with consecutive elements when trying to maximize frequency.
nums.sort()
s = list(accumulate(nums, initial=0))
The prefix sum array s
helps us quickly calculate the sum of any subarray. Here, s[i]
represents the sum of the first i
elements (with s[0] = 0
).
Step 2: Set up binary search
We binary search on the answer - the maximum possible frequency. The search range is from 1
(minimum possible frequency) to n
(maximum possible frequency if all elements can be made equal).
l, r = 1, n
Step 3: Define the check function
For a given frequency m
, we need to check if it's achievable. We slide a window of size m
through the sorted array:
def check(m: int) -> bool:
for i in range(m, n + 1):
if nums[i - 1] * m - (s[i] - s[i - m]) <= k:
return True
return False
For each window ending at index i
:
- The window contains elements from index
i-m
toi-1
- The target value is
nums[i-1]
(the largest element in the window) - The current sum of the window is
s[i] - s[i-m]
- The cost to make all elements equal to
nums[i-1]
isnums[i-1] * m - (s[i] - s[i-m])
- If this cost is at most
k
, then frequencym
is achievable
Step 4: Binary search execution
while l < r: mid = (l + r + 1) >> 1 if check(mid): l = mid else: r = mid - 1
We use the binary search pattern where:
- If frequency
mid
is achievable, we know all smaller frequencies are also achievable, so we search for larger frequencies by settingl = mid
- If frequency
mid
is not achievable, we need to search for smaller frequencies by settingr = mid - 1
- We use
(l + r + 1) >> 1
to avoid infinite loops whenl
andr
are adjacent
Step 5: Return the result
After the binary search converges, l
contains the maximum achievable frequency.
The time complexity is O(n log n)
for sorting plus O(n log n)
for binary search (log n iterations, each checking n windows), giving overall O(n log n)
. The space complexity is O(n)
for the prefix sum array.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with nums = [1, 4, 8, 13]
and k = 5
.
Step 1: Sort and compute prefix sums
- Array is already sorted:
[1, 4, 8, 13]
- Prefix sums:
s = [0, 1, 5, 13, 26]
s[0] = 0
s[1] = 0 + 1 = 1
s[2] = 1 + 4 = 5
s[3] = 5 + 8 = 13
s[4] = 13 + 13 = 26
Step 2: Binary search setup
- Search range:
l = 1, r = 4
(minimum frequency 1, maximum frequency 4)
Step 3: Binary search execution
First iteration: mid = (1 + 4 + 1) / 2 = 3
- Check if frequency 3 is achievable:
- Window 1: indices 0-2, elements
[1, 4, 8]
- Target:
nums[2] = 8
- Cost:
8 × 3 - (s[3] - s[0]) = 24 - 13 = 11
- 11 > 5, too expensive
- Target:
- Window 2: indices 1-3, elements
[4, 8, 13]
- Target:
nums[3] = 13
- Cost:
13 × 3 - (s[4] - s[1]) = 39 - 25 = 14
- 14 > 5, too expensive
- Target:
- Frequency 3 is NOT achievable, so
r = 2
- Window 1: indices 0-2, elements
Second iteration: mid = (1 + 2 + 1) / 2 = 2
- Check if frequency 2 is achievable:
- Window 1: indices 0-1, elements
[1, 4]
- Target:
nums[1] = 4
- Cost:
4 × 2 - (s[2] - s[0]) = 8 - 5 = 3
- 3 ≤ 5, achievable! ✓
- Target:
- Frequency 2 is achievable, so
l = 2
- Window 1: indices 0-1, elements
Third iteration: l = 2, r = 2
, loop exits
Result: Maximum frequency = 2
We can verify: With 3 operations, we can increment 1→2→3→4
, giving us [4, 4, 8, 13]
with frequency 2 for element 4.
Solution Implementation
1class Solution:
2 def maxFrequency(self, nums: List[int], k: int) -> int:
3 # Sort the array to group similar elements together
4 nums.sort()
5 n = len(nums)
6
7 # Build prefix sum array for quick range sum queries
8 # prefix_sum[i] stores sum of nums[0] to nums[i-1]
9 prefix_sum = [0]
10 for num in nums:
11 prefix_sum.append(prefix_sum[-1] + num)
12
13 def can_achieve_frequency(frequency: int) -> bool:
14 """
15 Check if we can make any element appear 'frequency' times
16 using at most k operations.
17
18 Strategy: For each window of size 'frequency', check if we can
19 make all elements equal to the maximum element in that window.
20 """
21 # Try each possible window of size 'frequency'
22 for right in range(frequency, n + 1):
23 left = right - frequency
24
25 # Sum of elements in current window
26 window_sum = prefix_sum[right] - prefix_sum[left]
27
28 # Target: make all elements equal to nums[right-1] (max in sorted window)
29 target_element = nums[right - 1]
30
31 # Cost to make all elements in window equal to target_element
32 operations_needed = target_element * frequency - window_sum
33
34 if operations_needed <= k:
35 return True
36
37 return False
38
39 # Binary search for the maximum achievable frequency
40 left_bound, right_bound = 1, n
41
42 while left_bound < right_bound:
43 # Use ceiling division to bias toward upper half
44 mid = (left_bound + right_bound + 1) // 2
45
46 if can_achieve_frequency(mid):
47 # If we can achieve 'mid' frequency, try for higher
48 left_bound = mid
49 else:
50 # If we cannot achieve 'mid' frequency, try lower
51 right_bound = mid - 1
52
53 return left_bound
54
1class Solution {
2 private int[] nums;
3 private long[] prefixSum;
4 private int maxOperations;
5
6 public int maxFrequency(int[] nums, int k) {
7 this.maxOperations = k;
8 this.nums = nums;
9
10 // Sort the array to make elements we want to increase adjacent
11 Arrays.sort(nums);
12
13 int n = nums.length;
14
15 // Build prefix sum array for range sum queries
16 // prefixSum[i] represents sum of elements from index 0 to i-1
17 prefixSum = new long[n + 1];
18 for (int i = 1; i <= n; i++) {
19 prefixSum[i] = prefixSum[i - 1] + nums[i - 1];
20 }
21
22 // Binary search on the answer (maximum frequency)
23 int left = 1, right = n;
24 while (left < right) {
25 // Calculate mid point, biased towards right when even
26 int mid = (left + right + 1) >> 1;
27
28 // Check if frequency of 'mid' is achievable
29 if (canAchieveFrequency(mid)) {
30 // If achievable, try for a larger frequency
31 left = mid;
32 } else {
33 // If not achievable, reduce the frequency
34 right = mid - 1;
35 }
36 }
37
38 return left;
39 }
40
41 /**
42 * Check if we can achieve a frequency of 'targetFreq' for any element
43 * @param targetFreq the target frequency to check
44 * @return true if achievable, false otherwise
45 */
46 private boolean canAchieveFrequency(int targetFreq) {
47 // Try each possible window of size 'targetFreq'
48 for (int i = targetFreq; i <= nums.length; i++) {
49 // Make all elements in window [i-targetFreq, i-1] equal to nums[i-1] (the maximum)
50 // Cost = (target value * count) - (sum of current values in window)
51 long currentSum = prefixSum[i] - prefixSum[i - targetFreq];
52 long targetSum = 1L * nums[i - 1] * targetFreq;
53 long operationsNeeded = targetSum - currentSum;
54
55 // Check if we can achieve this with at most k operations
56 if (operationsNeeded <= maxOperations) {
57 return true;
58 }
59 }
60 return false;
61 }
62}
63
1class Solution {
2public:
3 int maxFrequency(vector<int>& nums, int k) {
4 int n = nums.size();
5
6 // Sort the array to group similar elements together
7 sort(nums.begin(), nums.end());
8
9 // Build prefix sum array for quick range sum calculation
10 // prefixSum[i] stores the sum of first i elements
11 vector<long long> prefixSum(n + 1, 0);
12 for (int i = 1; i <= n; ++i) {
13 prefixSum[i] = prefixSum[i - 1] + nums[i - 1];
14 }
15
16 // Binary search on the answer (maximum frequency)
17 int left = 1, right = n;
18
19 // Lambda function to check if frequency 'freq' is achievable
20 // Returns true if we can make at least 'freq' elements equal using at most k operations
21 auto canAchieveFrequency = [&](int freq) -> bool {
22 // Try each window of size 'freq'
23 for (int i = freq; i <= n; ++i) {
24 // For window ending at index i-1 (0-indexed)
25 // We want to make all elements equal to nums[i-1] (the maximum in the window)
26
27 // Calculate operations needed:
28 // Target sum = nums[i-1] * freq (all elements become nums[i-1])
29 // Current sum = prefixSum[i] - prefixSum[i - freq]
30 // Operations needed = Target sum - Current sum
31 long long targetSum = 1LL * nums[i - 1] * freq;
32 long long currentSum = prefixSum[i] - prefixSum[i - freq];
33 long long operationsNeeded = targetSum - currentSum;
34
35 if (operationsNeeded <= k) {
36 return true;
37 }
38 }
39 return false;
40 };
41
42 // Binary search to find maximum achievable frequency
43 while (left < right) {
44 int mid = (left + right + 1) / 2;
45
46 if (canAchieveFrequency(mid)) {
47 // If frequency 'mid' is achievable, try for a larger frequency
48 left = mid;
49 } else {
50 // If frequency 'mid' is not achievable, search for smaller frequency
51 right = mid - 1;
52 }
53 }
54
55 return left;
56 }
57};
58
1/**
2 * Finds the maximum frequency of any element after performing at most k operations.
3 * Each operation allows incrementing any element by 1.
4 *
5 * @param nums - Array of integers to process
6 * @param k - Maximum sum of increments allowed
7 * @returns Maximum possible frequency of any element
8 */
9function maxFrequency(nums: number[], k: number): number {
10 const arrayLength = nums.length;
11
12 // Sort array in ascending order to group similar values together
13 nums.sort((a, b) => a - b);
14
15 // Build prefix sum array for range sum calculations
16 // prefixSum[i] represents sum of first i elements
17 const prefixSum: number[] = Array(arrayLength + 1).fill(0);
18 for (let i = 1; i <= arrayLength; ++i) {
19 prefixSum[i] = prefixSum[i - 1] + nums[i - 1];
20 }
21
22 // Binary search bounds for finding maximum frequency
23 let left = 1;
24 let right = arrayLength;
25
26 /**
27 * Checks if it's possible to achieve frequency of targetFrequency
28 * for any element using at most k operations
29 *
30 * @param targetFrequency - The frequency to check
31 * @returns true if achievable, false otherwise
32 */
33 const isFrequencyAchievable = (targetFrequency: number): boolean => {
34 // Try each possible window of size targetFrequency
35 for (let endIndex = targetFrequency; endIndex <= arrayLength; ++endIndex) {
36 // Calculate cost to make all elements in window equal to the maximum element
37 // Maximum element is at nums[endIndex - 1] (rightmost in sorted window)
38 const targetValue = nums[endIndex - 1];
39 const windowSum = prefixSum[endIndex] - prefixSum[endIndex - targetFrequency];
40 const costToEqualize = targetValue * targetFrequency - windowSum;
41
42 // Check if cost is within budget
43 if (costToEqualize <= k) {
44 return true;
45 }
46 }
47 return false;
48 };
49
50 // Binary search for maximum achievable frequency
51 while (left < right) {
52 // Calculate mid point (biased towards upper half)
53 const mid = (left + right + 1) >> 1;
54
55 if (isFrequencyAchievable(mid)) {
56 // If mid frequency is achievable, try higher frequencies
57 left = mid;
58 } else {
59 // If mid frequency is not achievable, try lower frequencies
60 right = mid - 1;
61 }
62 }
63
64 return left;
65}
66
Time and Space Complexity
Time Complexity: O(n × log n)
The time complexity consists of two main parts:
- Sorting the array
nums
takesO(n × log n)
time - Binary search runs
O(log n)
iterations, where each iteration calls thecheck
function - The
check
function iterates through at mostn
elements in its for loop, takingO(n)
time - Therefore, the binary search portion takes
O(n × log n)
time - The overall time complexity is
O(n × log n) + O(n × log n) = O(n × log n)
Space Complexity: O(n)
The space complexity analysis:
- The prefix sum array
s
storesn + 1
elements, requiringO(n)
space - The sorting operation may require
O(log n)
space for the recursion stack (depending on the sorting algorithm implementation) - Other variables use constant space
O(1)
- The dominant space usage is the prefix sum array, giving an overall space complexity of
O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Binary Search Boundary Update
One of the most common mistakes is using the wrong binary search pattern, particularly with the mid calculation and boundary updates:
Incorrect Implementation:
while left_bound < right_bound: mid = (left_bound + right_bound) // 2 # Floor division if can_achieve_frequency(mid): left_bound = mid # This creates infinite loop! else: right_bound = mid - 1
Problem: When left_bound = right_bound - 1
and can_achieve_frequency(mid)
returns True, setting left_bound = mid
doesn't change anything since mid = (left_bound + right_bound) // 2 = left_bound
, causing an infinite loop.
Solution: Use ceiling division when updating left_bound = mid
:
while left_bound < right_bound: mid = (left_bound + right_bound + 1) // 2 # Ceiling division if can_achieve_frequency(mid): left_bound = mid else: right_bound = mid - 1
2. Integer Overflow in Cost Calculation
When calculating the operations needed, the multiplication target_element * frequency
can cause integer overflow in languages with fixed integer sizes:
Problematic Code (in languages like C++ or Java):
operations_needed = target_element * frequency - window_sum
Solution: Use appropriate data types or check for overflow:
# In Python, this isn't an issue due to arbitrary precision integers # But in other languages, use long/int64 or check for overflow: if target_element > (k + window_sum) // frequency: return False # Would overflow or exceed k operations_needed = target_element * frequency - window_sum
3. Off-by-One Error in Window Indexing
A subtle but critical mistake is incorrectly calculating window boundaries or the target element:
Incorrect:
for right in range(frequency - 1, n): # Wrong starting point
left = right - frequency + 1
window_sum = prefix_sum[right + 1] - prefix_sum[left] # Wrong indices
target_element = nums[right] # Might not be the maximum
Solution: Ensure correct window boundaries:
for right in range(frequency, n + 1): # right is exclusive end
left = right - frequency # left is inclusive start
window_sum = prefix_sum[right] - prefix_sum[left]
target_element = nums[right - 1] # Maximum element in sorted window
4. Forgetting to Sort the Array
The algorithm relies on the array being sorted to ensure that the rightmost element in any window is the maximum:
Incorrect:
def maxFrequency(self, nums: List[int], k: int) -> int:
# Missing: nums.sort()
n = len(nums)
prefix_sum = [0]
# ... rest of the code
Solution: Always sort the array first:
nums.sort() # Critical for the algorithm to work correctly
5. Incorrect Prefix Sum Initialization
Using the wrong initial value or building the prefix sum incorrectly:
Incorrect:
prefix_sum = list(accumulate(nums)) # No initial 0
# or
prefix_sum = [nums[0]]
for i in range(1, n):
prefix_sum.append(prefix_sum[-1] + nums[i])
Solution: Initialize with 0 for easier range sum calculation:
prefix_sum = [0] for num in nums: prefix_sum.append(prefix_sum[-1] + num) # Now prefix_sum[i] - prefix_sum[j] gives sum from index j to i-1
A person thinks of a number between 1 and 1000. You may ask any number questions to them, provided that the question can be answered with either "yes" or "no".
What is the minimum number of questions you needed to ask so that you are guaranteed to know the number that the person is thinking?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
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
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
Want a Structured Path to Master System Design Too? Don’t Miss This!