Apply Operations to Maximize Frequency Score
HardPrompt
You are given a 0-indexed integer array nums and an integer k.
You can perform the following operation on the array at most k times:
- Choose any index
ifrom the array and increase or decreasenums[i]by1.
The score of the final array is the frequency of the most frequent element in the array.
Return the maximum score you can achieve.
The frequency of an element is the number of occurences of that element in the array.
Example 1:
Input: nums = [1,2,6,4], k = 3
Output: 3
Explanation: We can do the following operations on the array:
- Choose i = 0, and increase the value of nums[0] by 1. The resulting array is [2,2,6,4].
- Choose i = 3, and decrease the value of nums[3] by 1. The resulting array is [2,2,6,3].
- Choose i = 3, and decrease the value of nums[3] by 1. The resulting array is [2,2,6,2].
The element 2 is the most frequent in the final array so our score is 3.
It can be shown that we cannot achieve a better score.Example 2:
Input: nums = [1,4,4,2,4], k = 0
Output: 3
Explanation: We cannot apply any operations so our score will be the frequency of the most frequent element in the original array, which is 3.
Constraints:
1 <= nums.length <= 1051 <= nums[i] <= 1090 <= k <= 1014
Approaches
3 approaches with complexity analysis and trade-offs.
This approach iterates through all possible contiguous subarrays of the sorted input array. For each subarray, it calculates the minimum cost to make all its elements equal. The most efficient way to make a set of numbers equal is to change them all to their median. If this minimum cost is less than or equal to k, the length of that subarray is a possible frequency. We keep track of the maximum such length found.
Algorithm
- Sort the input array
nums. - Create a prefix sum array
prefixto quickly calculate the sum of elements in any subarray.prefix[i]will store the sum ofnums[0]tonums[i-1]. - Initialize
maxFreq = 0. If the array is not empty, the answer is at least 1. - Use nested loops to define the start
iand endjof every possible contiguous subarray. - For each subarray
nums[i...j]:- Calculate its length
len = j - i + 1. - Find the median element, which for a sorted subarray is at index
i + (len - 1) / 2. - Calculate the cost to make all elements in the subarray equal to the median value. This cost is the sum of differences:
sum(|nums[k] - median|)forkfromitoj. This can be computed inO(1)using the prefix sum array. - If the calculated
costis less than or equal tok, it means a frequency oflenis achievable. UpdatemaxFreq = max(maxFreq, len).
- Calculate its length
- After checking all subarrays,
maxFreqholds the result.
Walkthrough
The fundamental idea is that to achieve a frequency of f, we must choose f numbers from the array and make them identical. The cost of this operation is minimized if the chosen numbers are contiguous in the sorted version of the array, and the target value is their median.
This brute-force method systematically checks this for every possible contiguous subarray.
- Sort the array: First, we sort
numsin non-decreasing order. This costsO(N log N). - Prefix Sums: To avoid re-calculating sums for each subarray, we precompute a prefix sum array.
prefix[i]will store the sum ofnums[0]...nums[i-1]. This allowsO(1)calculation of the sum of any subarray. - Iterate Subarrays: We use two nested loops to consider every subarray
nums[i...j]. - Calculate Cost: For each subarray, we find its median and calculate the cost to transform all its elements to that median. The cost for a subarray
nums[i...j]with medianmis(cost to raise elements smaller than m) + (cost to lower elements larger than m). With prefix sums, this is anO(1)operation. - Update Maximum Frequency: If the cost is within our budget
k, we update our answer with the current subarray's length.
import java.util.Arrays; class Solution { public int maxFrequencyScore(int[] nums, long k) { int n = nums.length; Arrays.sort(nums); long[] prefix = new long[n + 1]; for (int i = 0; i < n; i++) { prefix[i + 1] = prefix[i] + nums[i]; } int maxFreq = 0; if (n > 0) { maxFreq = 1; } for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { int len = j - i + 1; int medianIndex = i + (len - 1) / 2; long medianValue = nums[medianIndex]; // Cost for elements to the left of the median long leftSum = prefix[medianIndex] - prefix[i]; long costLeft = medianValue * (long)(medianIndex - i) - leftSum; // Cost for elements to the right of the median long rightSum = prefix[j + 1] - prefix[medianIndex + 1]; long costRight = rightSum - medianValue * (long)(j - medianIndex); if (costLeft + costRight <= k) { maxFreq = Math.max(maxFreq, len); } } } return maxFreq; }}Complexity
Time
O(N^2). Sorting takes `O(N log N)`. The nested loops to iterate through all subarrays run in `O(N^2)`. Inside the loops, the cost calculation is `O(1)` thanks to prefix sums. The overall complexity is dominated by the nested loops.
Space
O(N) to store the prefix sum array. If sorting is done in-place, it does not require extra space, otherwise it could be up to O(N) depending on the implementation.
Trade-offs
Pros
Relatively straightforward to understand and implement.
Correctly identifies the optimal substructure (contiguous subarrays in sorted array).
Cons
The
O(N^2)time complexity is too slow for the given constraints (Nup to 10^5) and will result in a Time Limit Exceeded (TLE) error.
A more efficient approach recognizes the monotonic nature of the problem. If we can achieve a frequency of f, we can certainly achieve any frequency smaller than f. This property allows us to binary search for the maximum possible frequency. The core of this approach is an efficient check function that determines if a given frequency is possible within the budget k.
Algorithm
- The possible answer (maximum frequency) lies in the range
[1, N]. - We can binary search on this range to find the maximum frequency
f. - For a given frequency
f(ourmidin the binary search), we need a functionisPossible(f)to check if it's achievable. isPossible(f)function:- To check if a frequency of
fis possible, we need to see if there's any contiguous subarray of lengthfin the sortednumsarray that can be made uniform with at mostkoperations. - We slide a window of fixed size
fover the sorted array. - For each window, we calculate the cost to change all its elements to its median. This is done in
O(1)using a precomputed prefix sum array. - If the cost for any window is
<= k, we returntrue. - If we check all windows and none satisfy the condition, we return
false.
- To check if a frequency of
- Based on the result of
isPossible(mid):- If
true, a frequency ofmidis possible, so we store it as a potential answer and try for a larger frequency (low = mid + 1). - If
false,midis too large, so we search in the lower half (high = mid - 1).
- If
Walkthrough
This approach transforms the problem from finding the maximum value to a series of decision problems.
- Sort and Prefix Sums: Like the previous approach, we begin by sorting
numsand creating a prefix sum array. These are done once. - Binary Search on Answer: We define a search space for the answer from
1toN. We use binary search to find the largest valuefin this range for whichisPossible(f)is true. - Feasibility Check
isPossible(f): This function checks if we can makefelements equal. It does this by iterating through all contiguous subarrays of lengthfin the sortednumsarray. For each subarray (window), it calculates the cost to make all its elements equal to their median. If any window's cost is at mostk, the function returnstrue.
This method is efficient because the feasibility check for a fixed window size f takes O(N) time, and we perform this check O(log N) times.
import java.util.Arrays; class Solution { public int maxFrequencyScore(int[] nums, long k) { int n = nums.length; Arrays.sort(nums); long[] prefix = new long[n + 1]; for (int i = 0; i < n; i++) { prefix[i + 1] = prefix[i] + nums[i]; } int low = 1, high = n, ans = 0; while (low <= high) { int mid = low + (high - low) / 2; if (isPossible(mid, nums, prefix, k)) { ans = mid; low = mid + 1; } else { high = mid - 1; } } return ans; } private boolean isPossible(int len, int[] nums, long[] prefix, long k) { if (len == 0) return true; int n = nums.length; for (int i = 0; i <= n - len; i++) { int j = i + len - 1; int medianIndex = i + (len - 1) / 2; long medianValue = nums[medianIndex]; long leftSum = prefix[medianIndex] - prefix[i]; long costLeft = medianValue * (long)(medianIndex - i) - leftSum; long rightSum = prefix[j + 1] - prefix[medianIndex + 1]; long costRight = rightSum - medianValue * (long)(j - medianIndex); if (costLeft + costRight <= k) { return true; } } return false; }}Complexity
Time
O(N log N). Sorting takes `O(N log N)`. The binary search performs `O(log N)` iterations. Inside each, the `isPossible` check takes `O(N)` time. The total complexity is `O(N log N + N log N) = O(N log N)`.
Space
O(N) for the prefix sum array.
Trade-offs
Pros
Efficient
O(N log N)time complexity, which passes the given constraints.Binary search is a standard and powerful technique for problems with monotonic properties.
Cons
The implementation is more complex than a simple brute-force approach.
While having the same asymptotic time complexity as the sliding window approach, it might be slightly slower in practice due to the logarithmic factor from the binary search calls.
This is the most optimal approach, which uses a sliding window technique. After sorting the array, we maintain a window [left, right]. We expand the window by moving the right pointer and shrink it by moving the left pointer. The goal is to find the largest possible window for which the cost of making all its elements uniform (equal to their median) does not exceed k.
Algorithm
- Sort the
numsarray and precompute the prefix sum array. - Initialize two pointers,
left = 0andright = 0, to represent the current windownums[left...right]. - Initialize
maxFreq = 0. - Iterate
rightfrom0toN-1to expand the window. - For the current window
[left, right], calculate the cost to make all its elements equal to their median. - If the
cost > k, the window is too expensive. Shrink the window from the left by incrementingleftuntil the cost is within the budgetk. - Once the cost for the window
[left, right]is valid (i.e.,<= k), its lengthright - left + 1is a possible frequency. UpdatemaxFreq = max(maxFreq, right - left + 1). - Continue until
righthas traversed the entire array.
Walkthrough
This approach provides a linear-time scan over the sorted array, making it very efficient.
- Sort and Prefix Sums: As with other approaches, we start by sorting
numsand building a prefix sum array forO(1)cost calculations. - Sliding Window: We use two pointers,
leftandright, to define a window. Therightpointer always moves forward, expanding the window. Theleftpointer moves forward only when the window needs to be shrunk. - Maintain Window Validity: For each position of
right, we have a window[left, right]. We calculate the cost to unify this window's elements to their median. If this cost exceedsk, we know the window is too large. We then incrementleft, effectively sliding the window forward and shrinking it, and re-evaluate the cost. We repeat this until the cost for the window[left, right]is valid. - Update Max Frequency: At each step of the outer loop (for
right), after we have adjustedleftto form a valid window, the lengthright - left + 1is the maximum possible frequency ending atright. We update our global maximum frequency with this length.
Because both left and right pointers only move forward, they each traverse the array at most once, leading to a linear time complexity for the windowing part.
import java.util.Arrays; class Solution { public int maxFrequencyScore(int[] nums, long k) { int n = nums.length; Arrays.sort(nums); long[] prefix = new long[n + 1]; for (int i = 0; i < n; i++) { prefix[i + 1] = prefix[i] + nums[i]; } int maxFreq = 0; int left = 0; for (int right = 0; right < n; right++) { // Keep shrinking the window from the left while the cost is too high while (calculateCost(left, right, nums, prefix) > k) { left++; } // The window [left, right] is now valid, update max frequency maxFreq = Math.max(maxFreq, right - left + 1); } return maxFreq; } private long calculateCost(int left, int right, int[] nums, long[] prefix) { int len = right - left + 1; int medianIndex = left + (len - 1) / 2; long medianValue = nums[medianIndex]; long leftSum = prefix[medianIndex] - prefix[left]; long costLeft = medianValue * (long)(medianIndex - left) - leftSum; long rightSum = prefix[right + 1] - prefix[medianIndex + 1]; long costRight = rightSum - medianValue * (long)(right - medianIndex); return costLeft + costRight; }}Complexity
Time
O(N log N). Sorting takes `O(N log N)`. The sliding window part takes `O(N)` time because each pointer, `left` and `right`, traverses the array at most once. The total complexity is `O(N log N)`.
Space
O(N) for the prefix sum array.
Trade-offs
Pros
Highly efficient with
O(N log N)time complexity, dominated by the initial sort.Often faster in practice than the binary search approach due to better constant factors (a single pass after sorting).
Cons
The logic of calculating the cost for a dynamically changing window and ensuring correctness can be slightly more complex to grasp than the binary search approach.
Solutions
Solution
class Solution {public int maxFrequencyScore(int[] nums, long k) { Arrays.sort(nums); int n = nums.length; long[] s = new long[n + 1]; for (int i = 1; i <= n; i++) { s[i] = s[i - 1] + nums[i - 1]; } int l = 0, r = n; while (l < r) { int mid = (l + r + 1) >> 1; boolean ok = false; for (int i = 0; i <= n - mid; i++) { int j = i + mid; int x = nums[(i + j) / 2]; long left = ((i + j) / 2 - i) * (long)x - (s[(i + j) / 2] - s[i]); long right = (s[j] - s[(i + j) / 2]) - ((j - (i + j) / 2) * (long)x); if (left + right <= k) { ok = true; break; } } if (ok) { l = mid; } else { r = mid - 1; } } return l; }}class Solution {public: int maxFrequencyScore(vector<int> &nums, long long k) { sort(nums.begin(), nums.end()); int n = nums.size(); vector<long long> s(n + 1, 0); for (int i = 1; i <= n; i++) { s[i] = s[i - 1] + nums[i - 1]; } int l = 0, r = n; while (l < r) { int mid = (l + r + 1) >> 1; bool ok = false; for (int i = 0; i <= n - mid; i++) { int j = i + mid; int x = nums[(i + j) / 2]; long long left = ((i + j) / 2 - i) * (long long)x - (s[(i + j) / 2] - s[i]); long long right = (s[j] - s[(i + j) / 2]) - ((j - (i + j) / 2) * (long long)x); if (left + right <= k) { ok = true; break; } } if (ok) { l = mid; } else { r = mid - 1; } } return l; }};class Solution: def maxFrequencyScore(self, nums: List[int], k: int) -> int: nums . sort() s = list(accumulate(nums, initial=0)) n = len(nums) l, r = 0, n while l < r: mid = (l + r + 1) >> 1 ok = False for i in range(n - mid + 1): j = i + mid x = nums[(i + j) // 2] left = ((i + j) // 2 - i) * x - (s[(i + j) // 2] - s[i]) right = (s[j] - s[(i + j) // 2]) - ((j - (i + j) // 2) * x) if left + right <= k: ok = True break if ok: l = mid else: r = mid - 1 return lVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.