Facebook Pixel

2968. Apply Operations to Maximize Frequency Score

Problem Description

You have an array of integers nums (0-indexed) and an integer k representing the maximum number of operations you can perform.

In each operation, you can:

  • Select any index i in the array
  • Either increase or decrease nums[i] by 1

Your goal is to maximize the frequency of the most frequent element in the final array. The frequency of an element is simply how many times it appears in the array.

For example, if you have nums = [1, 2, 4] and k = 5:

  • You could change the array to [2, 2, 2] by decreasing 4 twice (2 operations) and increasing 1 once (1 operation), using 3 operations total
  • This gives you a maximum frequency of 3 (the element 2 appears 3 times)

The key insight is that you want to make as many elements equal as possible within your operation budget k. Each operation moves an element one step closer to your target value, so elements that are already close to each other will require fewer operations to become equal.

The solution uses binary search to find the maximum possible frequency. For each candidate frequency, it checks if it's possible to create a subarray of that length where all elements can be made equal using at most k operations. The optimal strategy is to change all elements to the median value of the subarray, as this minimizes the total number of operations needed.

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

Intuition

The first key observation is that if we want to make multiple elements equal, it's most efficient to work with elements that are already close to each other in value. This suggests we should sort the array first, as consecutive elements in a sorted array have the smallest differences.

Once sorted, we realize that to maximize frequency, we should focus on making a contiguous segment of the sorted array equal. Why? Because if we try to make non-contiguous elements equal, we'd be wasting operations on elements in between that we're not including.

Now, what value should we change all elements in a segment to? The median is optimal. Consider a segment [1, 2, 5]:

  • If we change all to 1: operations needed = 0 + 1 + 4 = 5
  • If we change all to 2: operations needed = 1 + 0 + 3 = 4
  • If we change all to 5: operations needed = 4 + 3 + 0 = 7

The median minimizes the total distance (and thus operations) needed.

The next insight is about the search strategy. If we can achieve a frequency of x (meaning we can make x elements equal), then we can definitely achieve any frequency less than x - we'd just use fewer elements. This monotonic property screams binary search!

So our approach becomes:

  1. Binary search on the answer (the maximum frequency)
  2. For each candidate frequency mid, check if it's achievable
  3. To check if frequency mid is achievable, try all possible windows of size mid in the sorted array
  4. For each window, calculate the cost to make all elements equal to the median
  5. If any window has cost ≤ k, then frequency mid is achievable

The cost calculation uses prefix sums for efficiency. For a window from index i to j, we need:

  • Elements below the median to increase to reach the median
  • Elements above the median to decrease to reach the median

This gives us the formula: left + right where:

  • left = sum of differences between median and elements below it
  • right = sum of differences between elements above median and the median itself

Learn more about Binary Search, Prefix Sum, Sorting and Sliding Window patterns.

Solution Implementation

1class Solution:
2    def maxFrequencyScore(self, nums: List[int], k: int) -> int:
3        # Sort the array for efficient median-based calculations
4        nums.sort()
5
6        # Create prefix sum array for range sum queries
7        # prefix_sum[i] represents sum of elements from nums[0] to nums[i-1]
8        prefix_sum = list(accumulate(nums, initial=0))
9        n = len(nums)
10
11        def feasible(window_size: int) -> bool:
12            """Check if we can make window_size elements equal with at most k operations."""
13            # Try all possible windows of the given size
14            for window_start in range(n - window_size + 1):
15                window_end = window_start + window_size
16
17                # Find the median element's index in current window
18                median_index = (window_start + window_end) // 2
19                median_value = nums[median_index]
20
21                # Calculate cost to change all elements left of median to median value
22                left_count = median_index - window_start
23                left_sum = prefix_sum[median_index] - prefix_sum[window_start]
24                left_cost = left_count * median_value - left_sum
25
26                # Calculate cost to change all elements right of median to median value
27                right_count = window_end - median_index
28                right_sum = prefix_sum[window_end] - prefix_sum[median_index]
29                right_cost = right_sum - right_count * median_value
30
31                # Check if total cost is within budget k
32                if left_cost + right_cost <= k:
33                    return True
34            return False
35
36        # Binary search for maximum feasible frequency
37        # Pattern: true, true, ..., true, false, false (find last true)
38        left, right = 1, n
39        last_true_index = 0
40
41        while left <= right:
42            mid = (left + right) // 2
43            if feasible(mid):
44                last_true_index = mid
45                left = mid + 1  # Try larger frequency
46            else:
47                right = mid - 1  # Try smaller frequency
48
49        return last_true_index
50
1class Solution {
2    private int[] nums;
3    private long[] prefixSum;
4    private long k;
5
6    public int maxFrequencyScore(int[] nums, long k) {
7        // Sort the array to enable efficient calculation of operations needed
8        Arrays.sort(nums);
9        this.nums = nums;
10        this.k = k;
11        int n = nums.length;
12
13        // Build prefix sum array for quick range sum queries
14        prefixSum = new long[n + 1];
15        for (int i = 1; i <= n; i++) {
16            prefixSum[i] = prefixSum[i - 1] + nums[i - 1];
17        }
18
19        // Binary search for maximum feasible frequency
20        // Pattern: true, true, ..., true, false, false (find last true)
21        int left = 1;
22        int right = n;
23        int lastTrueIndex = 0;
24
25        while (left <= right) {
26            int mid = left + (right - left) / 2;
27            if (feasible(mid)) {
28                lastTrueIndex = mid;
29                left = mid + 1;  // Try larger frequency
30            } else {
31                right = mid - 1;  // Try smaller frequency
32            }
33        }
34
35        return lastTrueIndex;
36    }
37
38    private boolean feasible(int windowSize) {
39        int n = nums.length;
40        // Try all possible windows of the given size
41        for (int startIdx = 0; startIdx <= n - windowSize; startIdx++) {
42            int endIdx = startIdx + windowSize;
43
44            // Choose median as target value (optimal for minimizing total operations)
45            int medianIdx = (startIdx + endIdx) / 2;
46            int targetValue = nums[medianIdx];
47
48            // Calculate operations needed for left half (elements smaller than median)
49            long leftOperations = (long)(medianIdx - startIdx) * targetValue -
50                                  (prefixSum[medianIdx] - prefixSum[startIdx]);
51
52            // Calculate operations needed for right half (elements larger than median)
53            long rightOperations = (prefixSum[endIdx] - prefixSum[medianIdx]) -
54                                  (long)(endIdx - medianIdx) * targetValue;
55
56            // Check if total operations needed is within budget k
57            if (leftOperations + rightOperations <= k) {
58                return true;
59            }
60        }
61        return false;
62    }
63}
64
1class Solution {
2public:
3    int maxFrequencyScore(vector<int>& nums, long long k) {
4        // Sort the array to group similar values together
5        sort(nums.begin(), nums.end());
6
7        int n = nums.size();
8
9        // Build prefix sum array for efficient range sum queries
10        vector<long long> prefixSum(n + 1, 0);
11        for (int i = 1; i <= n; i++) {
12            prefixSum[i] = prefixSum[i - 1] + nums[i - 1];
13        }
14
15        // Feasibility check: can we make windowSize elements equal with at most k operations?
16        auto feasible = [&](int windowSize) -> bool {
17            for (int i = 0; i <= n - windowSize; i++) {
18                int j = i + windowSize;
19
20                // Choose median as the target value
21                int medianIndex = (i + j) / 2;
22                int targetValue = nums[medianIndex];
23
24                // Calculate cost for left half
25                long long leftCost = (long long)(medianIndex - i) * targetValue -
26                                    (prefixSum[medianIndex] - prefixSum[i]);
27
28                // Calculate cost for right half
29                long long rightCost = (prefixSum[j] - prefixSum[medianIndex]) -
30                                     (long long)(j - medianIndex) * targetValue;
31
32                if (leftCost + rightCost <= k) {
33                    return true;
34                }
35            }
36            return false;
37        };
38
39        // Binary search for maximum feasible frequency
40        // Pattern: true, true, ..., true, false, false (find last true)
41        int left = 1;
42        int right = n;
43        int lastTrueIndex = 0;
44
45        while (left <= right) {
46            int mid = left + (right - left) / 2;
47            if (feasible(mid)) {
48                lastTrueIndex = mid;
49                left = mid + 1;  // Try larger frequency
50            } else {
51                right = mid - 1;  // Try smaller frequency
52            }
53        }
54
55        return lastTrueIndex;
56    }
57};
58
1/**
2 * Finds the maximum frequency score achievable by performing at most k operations
3 * @param nums - Array of numbers to process
4 * @param k - Maximum number of operations allowed
5 * @returns Maximum frequency score (length of subarray that can be made equal)
6 */
7function maxFrequencyScore(nums: number[], k: number): number {
8    // Sort the array in ascending order
9    nums.sort((a, b) => a - b);
10
11    const n: number = nums.length;
12
13    // Build prefix sum array for quick range sum calculations
14    const prefixSum: number[] = Array(n + 1).fill(0);
15    for (let i = 1; i <= n; i++) {
16        prefixSum[i] = prefixSum[i - 1] + nums[i - 1];
17    }
18
19    // Feasibility check: can we make windowSize elements equal with at most k operations?
20    const feasible = (windowSize: number): boolean => {
21        for (let startIdx = 0; startIdx <= n - windowSize; startIdx++) {
22            const endIdx = startIdx + windowSize;
23
24            // Choose the median element as the target value
25            const medianIdx = Math.floor((startIdx + endIdx) / 2);
26            const targetValue = nums[medianIdx];
27
28            // Calculate operations needed for elements left of median
29            const leftOperations = (medianIdx - startIdx) * targetValue -
30                                  (prefixSum[medianIdx] - prefixSum[startIdx]);
31
32            // Calculate operations needed for elements right of median
33            const rightOperations = (prefixSum[endIdx] - prefixSum[medianIdx]) -
34                                   (endIdx - medianIdx) * targetValue;
35
36            if (leftOperations + rightOperations <= k) {
37                return true;
38            }
39        }
40        return false;
41    };
42
43    // Binary search for maximum feasible frequency
44    // Pattern: true, true, ..., true, false, false (find last true)
45    let left = 1;
46    let right = n;
47    let lastTrueIndex = 0;
48
49    while (left <= right) {
50        const mid = Math.floor((left + right) / 2);
51        if (feasible(mid)) {
52            lastTrueIndex = mid;
53            left = mid + 1;  // Try larger frequency
54        } else {
55            right = mid - 1;  // Try smaller frequency
56        }
57    }
58
59    return lastTrueIndex;
60}
61

Solution Approach

Let's walk through the implementation step by step:

1. Sorting and Prefix Sum Setup

First, we sort the array to group similar values together:

nums.sort()

Then we build a prefix sum array for efficient range sum queries:

s = list(accumulate(nums, initial=0))

This allows us to calculate the sum of any subarray nums[i:j] as s[j] - s[i] in O(1) time.

2. Binary Search Framework

We binary search on the possible frequency values:

l, r = 0, n
while l < r:
    mid = (l + r + 1) >> 1
  • l = 0: minimum possible frequency
  • r = n: maximum possible frequency (all elements become equal)
  • mid = (l + r + 1) >> 1: middle value with upward rounding

3. Checking Feasibility of a Frequency

For each candidate frequency mid, we check all possible windows of size mid:

for i in range(n - mid + 1):
    j = i + mid
    x = nums[(i + j) // 2]  # median element

4. Calculating Operations Needed

For each window [i, j), we calculate the cost to make all elements equal to the median x:

left = ((i + j) // 2 - i) * x - (s[(i + j) // 2] - s[i])
right = (s[j] - s[(i + j) // 2]) - ((j - (i + j) // 2) * x)

Breaking down the formulas:

  • Left cost: Elements from index i to (i+j)//2 - 1 need to increase to x

    • Number of elements: (i + j) // 2 - i
    • Target sum if all were x: ((i + j) // 2 - i) * x
    • Current sum: s[(i + j) // 2] - s[i]
    • Operations needed: target sum - current sum
  • Right cost: Elements from index (i+j)//2 + 1 to j-1 need to decrease to x

    • Current sum: s[j] - s[(i + j) // 2]
    • Target sum if all were x: (j - (i + j) // 2) * x
    • Operations needed: current sum - target sum

5. Binary Search Decision

If any window can be transformed with at most k operations:

if left + right <= k:
    ok = True
    break

Based on feasibility:

  • If feasible (ok = True): This frequency works, try larger (l = mid)
  • If not feasible: This frequency is too large, try smaller (r = mid - 1)

Time Complexity: O(n² log n)

  • Binary search: O(log n) iterations
  • Each iteration checks O(n) windows
  • Each window check: O(1) using prefix sums

Space Complexity: O(n) for the prefix sum array

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 a concrete example with nums = [1, 4, 8, 13] and k = 5.

Step 1: Sort and Build Prefix Sum

  • Array is already sorted: [1, 4, 8, 13]
  • Prefix sum array: s = [0, 1, 5, 13, 26]
    • This allows us to quickly calculate range sums

Step 2: Binary Search on Frequency

We'll binary search between frequency 1 and 4 (array length).

First iteration: mid = 2

  • Can we make 2 elements equal with ≤ 5 operations?

  • Check all windows of size 2:

    Window [1, 4] (indices 0-1):

    • Median element: nums[0] = 1
    • Cost to make both equal to 1: We need to decrease 4 to 1
    • Operations = 3 ✓ (≤ 5, so frequency 2 is achievable)

Second iteration: mid = 3

  • Can we make 3 elements equal with ≤ 5 operations?

  • Check all windows of size 3:

    Window [1, 4, 8] (indices 0-2):

    • Median element: nums[1] = 4
    • Cost calculation:
      • Left side: Change 1 to 4 = 3 operations
      • Right side: Change 8 to 4 = 4 operations
      • Total = 7 operations ✗ (> 5)

    Window [4, 8, 13] (indices 1-3):

    • Median element: nums[2] = 8
    • Cost calculation:
      • Left side: Change 4 to 8 = 4 operations
      • Right side: Change 13 to 8 = 5 operations
      • Total = 9 operations ✗ (> 5)

    Frequency 3 is not achievable with k=5.

Binary Search Continues:

  • Since frequency 3 failed, we search lower
  • We already know frequency 2 works
  • Binary search converges to answer: 2

Verification of the Answer: With k=5 operations, we can transform [1, 4, 8, 13] to have at most frequency 2:

  • One possibility: Change 4 to 1 (3 operations) → [1, 1, 8, 13]
  • Another possibility: Change 8 to 4 (4 operations) → [1, 4, 4, 13]

The maximum frequency achievable is 2.

Template Solution (Last True Pattern)

def maxFrequencyScore(self, nums: List[int], k: int) -> int:
    nums.sort()
    n = len(nums)
    prefix_sum = list(accumulate(nums, initial=0))

    def feasible(window_size: int) -> bool:
        for i in range(n - window_size + 1):
            j = i + window_size
            median_idx = (i + j) // 2
            median = nums[median_idx]

            left_cost = (median_idx - i) * median - (prefix_sum[median_idx] - prefix_sum[i])
            right_cost = (prefix_sum[j] - prefix_sum[median_idx]) - (j - median_idx) * median

            if left_cost + right_cost <= k:
                return True
        return False

    # Binary search: find last true (maximum feasible frequency)
    left, right = 1, n
    last_true_index = 0

    while left <= right:
        mid = (left + right) // 2
        if feasible(mid):
            last_true_index = mid
            left = mid + 1  # Try larger
        else:
            right = mid - 1  # Try smaller

    return last_true_index

Time and Space Complexity

Time Complexity: O(n × log n)

The time complexity consists of two main parts:

  • Sorting the array takes O(n log n) time
  • Binary search on the answer space: The binary search runs for O(log n) iterations (searching from 0 to n for the maximum frequency). In each iteration, we check all possible subarrays of length mid, which takes O(n) time. Therefore, this part contributes O(n × log n) time.

Since both operations are O(n × log n), the overall time complexity is O(n × log n).

Space Complexity: O(n)

The space complexity comes from:

  • The prefix sum array s which stores n+1 elements, requiring O(n) space
  • The sorting operation may require O(log n) space for the recursion stack in the worst case (depending on the sorting algorithm implementation)
  • Other variables like l, r, mid, i, j, x, left, right, and ok use O(1) space

The dominant term is O(n) from the prefix sum array, so the overall space complexity is O(n).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Using Wrong Binary Search Template Variant

This is a maximization problem (find largest feasible frequency), which requires the "last true" pattern. Using the "first true" pattern will give incorrect results.

The Issue:

# WRONG: "first true" pattern (for minimization)
first_true_index = -1
while left <= right:
    mid = (left + right) // 2
    if feasible(mid):
        first_true_index = mid
        right = mid - 1  # Wrong direction!
    else:
        left = mid + 1

Solution: Use the "last true" pattern for maximization:

# CORRECT: "last true" pattern (for maximization)
last_true_index = 0
while left <= right:
    mid = (left + right) // 2
    if feasible(mid):
        last_true_index = mid
        left = mid + 1  # Try larger values
    else:
        right = mid - 1  # Try smaller values

2. Incorrect Median Index Calculation for Even-Length Windows

One of the most common mistakes is mishandling the median index calculation when the window has an even number of elements.

The Issue: For even-length windows, there are two middle elements. The formula (i + j) // 2 gives us the upper median index. This inconsistency can lead to incorrect cost calculations.

Solution: Be explicit about which median you're using and ensure consistency:

# For a window [window_start, window_end)
median_index = (window_start + window_end) // 2

3. Confusion with Prefix Sum Boundaries

The prefix sum array uses initial=0, making prefix_sum[i] represent the sum of elements from index 0 to i-1 (exclusive of i).

The Issue:

# Wrong: sum from i to j inclusive
wrong_sum = prefix_sum[j] - prefix_sum[i]  # This actually gives sum from i to j-1

# Correct: sum from i to j inclusive
correct_sum = prefix_sum[j + 1] - prefix_sum[i]

4. Not Defining a Clear Feasible Function

Mixing the feasibility check into the binary search loop makes the code harder to reason about and more error-prone.

Solution: Extract the feasibility check into a separate function:

def feasible(window_size: int) -> bool:
    """Check if we can make window_size elements equal with at most k operations."""
    for window_start in range(n - window_size + 1):
        # ... check each window
        if cost <= k:
            return True
    return False

5. Integer Overflow in Cost Calculations

For large arrays with large values, the cost calculations might overflow in languages with fixed integer sizes.

Solution (for languages with overflow concerns): Use long long in C++ or long in Java for storing sums and products.

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

What's the output of running the following function using the following tree as input?

1def serialize(root):
2    res = []
3    def dfs(root):
4        if not root:
5            res.append('x')
6            return
7        res.append(root.val)
8        dfs(root.left)
9        dfs(root.right)
10    dfs(root)
11    return ' '.join(res)
12
1import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4    StringJoiner res = new StringJoiner(" ");
5    serializeDFS(root, res);
6    return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10    if (root == null) {
11        result.add("x");
12        return;
13    }
14    result.add(Integer.toString(root.val));
15    serializeDFS(root.left, result);
16    serializeDFS(root.right, result);
17}
18
1function serialize(root) {
2    let res = [];
3    serialize_dfs(root, res);
4    return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8    if (!root) {
9        res.push("x");
10        return;
11    }
12    res.push(root.val);
13    serialize_dfs(root.left, res);
14    serialize_dfs(root.right, res);
15}
16

Recommended Readings

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

Load More