# 2968. Apply Operations to Maximize Frequency Score

## Description

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 i from the array and increase or decrease nums[i] by 1.

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 <= 105
• 1 <= nums[i] <= 109
• 0 <= k <= 1014

## Solutions

Solution 1: Sorting + Prefix Sum + Binary Search

The problem asks for the maximum frequency of the mode we can get after performing at most $k$ operations. If we sort the array $nums$ in ascending order, it would be best to turn a continuous segment of numbers into the same number, which can reduce the number of operations and increase the frequency of the mode.

Therefore, we might as well sort the array $nums$ first.

Next, we analyze that if a frequency $x$ is feasible, then for any $y \le x$, the frequency $y$ is also feasible, which shows monotonicity. Therefore, we can use binary search to find the maximum feasible frequency.

We binary search the frequency, define the left boundary of the binary search as $l = 0$, and the right boundary as $r = n$, where $n$ is the length of the array. In each binary search process, we take the middle value $mid = \lfloor \frac{l + r + 1}{2} \rfloor$, and then determine whether there exists a continuous subarray of length $mid$ in $nums$, such that all elements in this subarray become the median of this subarray, and the number of operations does not exceed $k$. If it exists, then we update the left boundary $l$ to $mid$, otherwise we update the right boundary $r$ to $mid - 1$.

To determine whether such a subarray exists, we can use prefix sum. We first define two pointers $i$ and $j$, initially $i = 0$, $j = i + mid$. Then all elements from $nums[i]$ to $nums[j - 1]$ are changed to $nums[(i + j) / 2]$, and the number of operations required is $left + right$, where:

\begin{aligned} \text{left} &= \sum_{k = i}^{(i + j) / 2 - 1} (nums[(i + j) / 2] - nums[k]) \\ &= ((i + j) / 2 - i) \times nums[(i + j) / 2] - \sum_{k = i}^{(i + j) / 2 - 1} nums[k] \end{aligned} \begin{aligned} \text{right} &= \sum_{k = (i + j) / 2 + 1}^{j} (nums[k] - nums[(i + j) / 2]) \\ &= \sum_{k = (i + j) / 2 + 1}^{j} nums[k] - (j - (i + j) / 2) \times nums[(i + j) / 2] \end{aligned}

We can use the prefix sum array $s$ to calculate $\sum_{k = i}^{j} nums[k]$, so as to calculate $left$ and $right$ in $O(1)$ time.

The time complexity is $O(n \times \log n)$, and the space complexity is $O(n)$. Here, $n$ is the length of the array.

• 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 l


• func maxFrequencyScore(nums []int, k int64) int {
sort.Ints(nums)
n := len(nums)
s := make([]int64, n+1)
for i := 1; i <= n; i++ {
s[i] = s[i-1] + int64(nums[i-1])
}

l, r := 0, n
for l < r {
mid := (l + r + 1) >> 1
ok := false

for i := 0; i <= n-mid; i++ {
j := i + mid
x := int64(nums[(i+j)/2])
left := (int64((i+j)/2-i) * x) - (s[(i+j)/2] - s[i])
right := (s[j] - s[(i+j)/2]) - (int64(j-(i+j)/2) * x)

if left+right <= k {
ok = true
break
}
}

if ok {
l = mid
} else {
r = mid - 1
}
}

return l
}

• function maxFrequencyScore(nums: number[], k: number): number {
nums.sort((a, b) => a - b);
const n = nums.length;
const s: number[] = Array(n + 1).fill(0);
for (let i = 1; i <= n; i++) {
s[i] = s[i - 1] + nums[i - 1];
}

let l: number = 0;
let r: number = n;
while (l < r) {
const mid: number = (l + r + 1) >> 1;
let ok: boolean = false;
for (let i = 0; i <= n - mid; i++) {
const j = i + mid;
const x = nums[Math.floor((i + j) / 2)];
const left = (Math.floor((i + j) / 2) - i) * x - (s[Math.floor((i + j) / 2)] - s[i]);
const right = s[j] - s[Math.floor((i + j) / 2)] - (j - Math.floor((i + j) / 2)) * x;
if (left + right <= k) {
ok = true;
break;
}
}
if (ok) {
l = mid;
} else {
r = mid - 1;
}
}

return l;
}