3347. Maximum Frequency of an Element After Performing Operations II
Problem Description
You have an integer array nums
and two integers k
and numOperations
.
You need to perform exactly numOperations
operations on the array. In each operation:
- Choose an index
i
that hasn't been selected in any previous operation (each index can only be selected once) - Add any integer value between
-k
andk
(inclusive) tonums[i]
After performing all operations, you want to maximize the frequency of any element in the array. The frequency of an element is the number of times it appears in the array.
Your task is to return the maximum possible frequency of any single element value that can be achieved after performing all the operations optimally.
For example, if you have nums = [1, 4, 5]
, k = 2
, and numOperations = 2
:
- You could change
nums[0]
from 1 to 3 (by adding 2) - You could change
nums[2]
from 5 to 3 (by adding -2) - This would give you
[3, 4, 3]
with a maximum frequency of 2 (the value 3 appears twice)
The key insight is that by adding values in the range [-k, k]
, each element can potentially be transformed to any value in the range [nums[i] - k, nums[i] + k]
. You need to find the optimal target value and operations to maximize how many elements can equal that target value.
Intuition
The key observation is that each element nums[i]
can be transformed to any value in the range [nums[i] - k, nums[i] + k]
if we choose to apply an operation on it. This means each element has a "reachable range" of values it can become.
To maximize the frequency of any value, we need to find a target value x
where the most elements can be transformed to reach x
. But we're limited by numOperations
- we can only transform at most numOperations
elements.
For any potential target value x
, we can count:
- How many elements are already equal to
x
(these don't need operations) - How many elements can reach
x
through their range[nums[i] - k, nums[i] + k]
The maximum frequency we can achieve for value x
is the minimum of:
- The total number of elements that can reach
x
(through their ranges) - The number of elements already at
x
plusnumOperations
(since we can only transformnumOperations
elements)
To efficiently compute this, we use a sweep line technique. For each element nums[i]
:
- It contributes to the "reachability count" for all values in range
[nums[i] - k, nums[i] + k]
- We mark the start of this range with
+1
and the end+1 with-1
By sorting these events and sweeping through them, we maintain a running sum s
that tells us how many elements can reach the current value. At each potential target value, we check if it can achieve a better maximum frequency than what we've seen so far.
The formula min(s, cnt[x] + numOperations)
captures the constraint: we can either transform all reachable elements to x
(if we have enough operations), or we keep the existing cnt[x]
elements and use our numOperations
to transform additional elements.
Learn more about Binary Search, Prefix Sum, Sorting and Sliding Window patterns.
Solution Approach
The solution uses a sweep line algorithm with difference arrays to efficiently compute the maximum frequency.
Step 1: Initialize Data Structures
cnt
: A dictionary to store the original frequency of each element innums
d
: A difference array (as a dictionary) to mark range boundaries
Step 2: Process Each Element
For each element x
in nums
:
- Increment
cnt[x]
to track its original frequency - Initialize
d[x]
to ensurex
is in our sweep line events - Mark the range
[x - k, x + k]
where this element can contribute:d[x - k] += 1
: Start of the reachable ranged[x + k + 1] -= 1
: End of the reachable range (exclusive)
Step 3: Sweep Line Processing
- Sort all the keys in
d
to process events in order - Maintain a running sum
s
that represents how many elements can reach the current value - For each value
x
and its difference valuet
:- Update the running sum:
s += t
- Calculate the maximum frequency possible at value
x
:s
represents total elements that can reachx
cnt[x] + numOperations
represents keeping originalx
values and transforming others- Take the minimum of these two values
- Update the global maximum:
ans = max(ans, min(s, cnt[x] + numOperations))
- Update the running sum:
Why This Works:
- The difference array technique allows us to efficiently track overlapping ranges
- At any point
x
, the values
tells us exactly how many elements from the original array can be transformed to reachx
- The constraint
min(s, cnt[x] + numOperations)
ensures we don't exceed our operation limit - By checking all potential target values (all event points in our sweep), we're guaranteed to find the optimal solution
Time Complexity: O(n log n)
due to sorting, where n
is the length of nums
Space Complexity: O(n)
for the dictionaries storing counts and events
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 a concrete example to illustrate the solution approach.
Example: nums = [1, 4, 5]
, k = 2
, numOperations = 2
Step 1: Initialize Data Structures
cnt = {}
(will store original frequencies)d = {}
(difference array for sweep line)
Step 2: Process Each Element
Processing nums[0] = 1
:
cnt[1] = 1
(frequency of 1 is now 1)d[1] = 0
(initialize)- Range where 1 can reach:
[1-2, 1+2] = [-1, 3]
d[-1] += 1
→d[-1] = 1
(start of range)d[4] -= 1
→d[4] = -1
(end+1 of range)
Processing nums[1] = 4
:
cnt[4] = 1
(frequency of 4 is now 1)d[4] = -1
(already exists from previous step)- Range where 4 can reach:
[4-2, 4+2] = [2, 6]
d[2] += 1
→d[2] = 1
(start of range)d[7] -= 1
→d[7] = -1
(end+1 of range)
Processing nums[2] = 5
:
cnt[5] = 1
(frequency of 5 is now 1)d[5] = 0
(initialize)- Range where 5 can reach:
[5-2, 5+2] = [3, 7]
d[3] += 1
→d[3] = 1
(start of range)d[8] -= 1
→d[8] = -1
(end+1 of range)
Current State:
cnt = {1: 1, 4: 1, 5: 1}
d = {-1: 1, 1: 0, 2: 1, 3: 1, 4: -1, 5: 0, 7: -1, 8: -1}
Step 3: Sweep Line Processing
Sort keys of d
: [-1, 1, 2, 3, 4, 5, 7, 8]
Initialize: s = 0
, ans = 0
Position | Value in d | s (after update) | cnt.get(pos, 0) | min(s, cnt[pos] + numOps) | ans |
---|---|---|---|---|---|
-1 | 1 | 1 | 0 | min(1, 0+2) = 1 | 1 |
1 | 0 | 1 | 1 | min(1, 1+2) = 1 | 1 |
2 | 1 | 2 | 0 | min(2, 0+2) = 2 | 2 |
3 | 1 | 3 | 0 | min(3, 0+2) = 2 | 2 |
4 | -1 | 2 | 1 | min(2, 1+2) = 2 | 2 |
5 | 0 | 2 | 1 | min(2, 1+2) = 2 | 2 |
7 | -1 | 1 | 0 | min(1, 0+2) = 1 | 2 |
8 | -1 | 0 | 0 | min(0, 0+2) = 0 | 2 |
Result: Maximum frequency = 2
Verification:
At position 3, we have s = 3
, meaning all three elements can reach value 3:
- Element 1 can reach 3 (by adding +2)
- Element 4 can reach 3 (by adding -1)
- Element 5 can reach 3 (by adding -2)
Since we only have 2 operations, we can transform 2 elements to value 3, achieving frequency 2. For instance, transform 1→3 and 5→3, resulting in array [3, 4, 3]
.
Solution Implementation
1from collections import defaultdict
2from typing import List
3
4class Solution:
5 def maxFrequency(self, nums: List[int], k: int, numOperations: int) -> int:
6 # Count frequency of each number in the original array
7 frequency_count = defaultdict(int)
8
9 # Difference array to track range contributions
10 # For each number x, we can change it to any value in [x-k, x+k]
11 difference_array = defaultdict(int)
12
13 for num in nums:
14 # Count original frequency
15 frequency_count[num] += 1
16
17 # Initialize difference array entry for this number
18 difference_array[num] += 0
19
20 # Mark the range [num - k, num + k] where this number can contribute
21 # +1 at the start of the range
22 difference_array[num - k] += 1
23 # -1 after the end of the range
24 difference_array[num + k + 1] -= 1
25
26 max_frequency = 0
27 cumulative_sum = 0
28
29 # Process points in sorted order to calculate maximum possible frequency
30 for value, delta in sorted(difference_array.items()):
31 # Update cumulative sum (number of elements that can be changed to current value)
32 cumulative_sum += delta
33
34 # Maximum frequency at this value is:
35 # - Original count plus operations (limited by numOperations)
36 # - Cannot exceed total available elements that can reach this value
37 max_frequency = max(
38 max_frequency,
39 min(cumulative_sum, frequency_count[value] + numOperations)
40 )
41
42 return max_frequency
43
1class Solution {
2 public int maxFrequency(int[] nums, int k, int numOperations) {
3 // Map to store the frequency of each number in the array
4 Map<Integer, Integer> frequencyMap = new HashMap<>();
5
6 // TreeMap to track range boundaries and their contributions
7 // Used for sweep line algorithm to count overlapping ranges
8 TreeMap<Integer, Integer> rangeBoundaries = new TreeMap<>();
9
10 // Process each number in the input array
11 for (int num : nums) {
12 // Count frequency of current number
13 frequencyMap.merge(num, 1, Integer::sum);
14
15 // Initialize the number itself as a potential target
16 rangeBoundaries.putIfAbsent(num, 0);
17
18 // Mark the start of range [num - k, num + k] where elements can be changed to num
19 // Increment at start of range (num - k)
20 rangeBoundaries.merge(num - k, 1, Integer::sum);
21
22 // Mark the end of range (exclusive) at num + k + 1
23 // Decrement after end of range
24 rangeBoundaries.merge(num + k + 1, -1, Integer::sum);
25 }
26
27 int maxResult = 0;
28 int currentOverlap = 0; // Running sum of overlapping ranges
29
30 // Sweep through all boundary points in sorted order
31 for (Map.Entry<Integer, Integer> entry : rangeBoundaries.entrySet()) {
32 int position = entry.getKey();
33 int delta = entry.getValue();
34
35 // Update the current overlap count
36 currentOverlap += delta;
37
38 // Calculate maximum frequency achievable at this position
39 // It's the minimum of:
40 // 1. Total elements that can be changed to this position (currentOverlap)
41 // 2. Original frequency + allowed operations
42 int originalFrequency = frequencyMap.getOrDefault(position, 0);
43 int achievableFrequency = Math.min(currentOverlap, originalFrequency + numOperations);
44
45 // Update the maximum frequency found so far
46 maxResult = Math.max(maxResult, achievableFrequency);
47 }
48
49 return maxResult;
50 }
51}
52
1class Solution {
2public:
3 int maxFrequency(vector<int>& nums, int k, int numOperations) {
4 // Map to store the frequency of each number in nums
5 unordered_map<int, int> frequency;
6
7 // Map to track range boundaries for sweep line algorithm
8 // Using map (ordered) to process events in sorted order
9 map<int, int> rangeBoundaries;
10
11 // Process each number in the input array
12 for (int num : nums) {
13 // Count frequency of current number
14 frequency[num]++;
15
16 // Initialize the number in rangeBoundaries (ensures it exists for later)
17 rangeBoundaries[num];
18
19 // Mark the start of range where current number can contribute
20 // Numbers in [num - k, num + k] can be changed to num
21 rangeBoundaries[num - k]++;
22
23 // Mark the end of range (exclusive) where current number stops contributing
24 rangeBoundaries[num + k + 1]--;
25 }
26
27 int maxResult = 0;
28 int activeCount = 0; // Count of numbers that can be changed to current position
29
30 // Sweep through all positions in sorted order
31 for (const auto& [position, delta] : rangeBoundaries) {
32 // Update the count of active numbers that can be changed to current position
33 activeCount += delta;
34
35 // Calculate max frequency achievable at this position:
36 // - frequency[position]: numbers already at this position
37 // - numOperations: max numbers we can change
38 // - activeCount: total numbers available to change to this position
39 // Take minimum of available changes and allowed operations
40 maxResult = max(maxResult, min(activeCount, frequency[position] + numOperations));
41 }
42
43 return maxResult;
44 }
45};
46
1/**
2 * Finds the maximum frequency of any element after performing at most numOperations
3 * increment/decrement operations within range k
4 * @param nums - Input array of numbers
5 * @param k - Maximum range for increment/decrement operations
6 * @param numOperations - Maximum number of operations allowed
7 * @returns Maximum achievable frequency of any element
8 */
9function maxFrequency(nums: number[], k: number, numOperations: number): number {
10 // Count frequency of each number in the array
11 const frequencyMap: Record<number, number> = {};
12
13 // Difference array to track range updates
14 // Used to efficiently calculate how many elements can be changed to value x
15 const differenceArray: Record<number, number> = {};
16
17 // Process each number in the input array
18 for (const num of nums) {
19 // Update frequency count for current number
20 frequencyMap[num] = (frequencyMap[num] || 0) + 1;
21
22 // Initialize difference array entry if not exists
23 differenceArray[num] = differenceArray[num] || 0;
24
25 // Mark range [num - k, num + k] where elements can be changed to num
26 // Increment at start of range
27 differenceArray[num - k] = (differenceArray[num - k] || 0) + 1;
28 // Decrement after end of range
29 differenceArray[num + k + 1] = (differenceArray[num + k + 1] || 0) - 1;
30 }
31
32 // Track maximum frequency found
33 let maxFrequencyResult: number = 0;
34
35 // Running sum for difference array (prefix sum technique)
36 let prefixSum: number = 0;
37
38 // Get all positions in sorted order for sweep line algorithm
39 const sortedPositions: number[] = Object.keys(differenceArray)
40 .map(Number)
41 .sort((a, b) => a - b);
42
43 // Process each position using sweep line technique
44 for (const position of sortedPositions) {
45 // Update prefix sum with difference value at current position
46 prefixSum += differenceArray[position];
47
48 // Calculate maximum frequency at current position:
49 // - prefixSum: total elements that can be changed to this position
50 // - frequencyMap[position]: elements already at this position
51 // - numOperations: maximum operations we can perform
52 // Take minimum of available changes and allowed operations, then add existing frequency
53 const currentFrequency: number = Math.min(
54 prefixSum,
55 (frequencyMap[position] || 0) + numOperations
56 );
57
58 // Update maximum frequency if current is better
59 maxFrequencyResult = Math.max(maxFrequencyResult, currentFrequency);
60 }
61
62 return maxFrequencyResult;
63}
64
Time and Space Complexity
Time Complexity: O(n log n)
where n
is the length of the input array nums
.
- Iterating through
nums
to populatecnt
andd
dictionaries takesO(n)
time - Each element in
nums
creates at most 3 entries in dictionaryd
(at positionsx
,x-k
, andx+k+1
), sod
has at mostO(n)
entries - Sorting the items in dictionary
d
takesO(n log n)
time since there are at mostO(n)
unique keys - The final iteration through sorted items takes
O(n)
time - Overall complexity is dominated by the sorting step:
O(n) + O(n log n) + O(n) = O(n log n)
Space Complexity: O(n)
where n
is the length of the input array nums
.
- Dictionary
cnt
stores at mostn
unique elements fromnums
, requiringO(n)
space - Dictionary
d
stores at most3n
entries (3 entries per element innums
), which isO(n)
space - The sorted items list created from
d.items()
requiresO(n)
space - Additional variables (
ans
,s
,x
,t
) useO(1)
space - Total space complexity:
O(n) + O(n) + O(n) + O(1) = O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Initialize Target Values in the Difference Array
A critical pitfall is forgetting to ensure that original values from nums
are included as event points in the difference array, even if they don't contribute any delta change initially.
Why this matters: If an element x
exists in nums
but isn't explicitly added to the difference array, it won't be considered as a potential target value during the sweep line processing. This can lead to missing the optimal solution when the best strategy is to keep some original values unchanged while transforming others to match them.
Incorrect approach:
# WRONG: Only adding range boundaries for num in nums: frequency_count[num] += 1 difference_array[num - k] += 1 difference_array[num + k + 1] -= 1
Correct approach:
# RIGHT: Explicitly initialize each num in difference_array for num in nums: frequency_count[num] += 1 difference_array[num] += 0 # Ensures num is a key in the dictionary difference_array[num - k] += 1 difference_array[num + k + 1] -= 1
2. Misunderstanding the Operation Constraint
Another common mistake is incorrectly applying the numOperations
constraint. The key insight is that we can perform at most numOperations
changes, and each operation changes exactly one element.
Incorrect interpretation:
# WRONG: Thinking we must use all operations
max_frequency = min(cumulative_sum, numOperations)
Correct interpretation:
# RIGHT: Keep original values and add transformed ones (up to numOperations)
max_frequency = min(cumulative_sum, frequency_count[value] + numOperations)
The formula frequency_count[value] + numOperations
represents:
frequency_count[value]
: Elements already at the target value (no operation needed)numOperations
: Maximum additional elements we can transform to this value
3. Off-by-One Error in Range Boundaries
When marking ranges in the difference array, it's crucial to use num + k + 1
(not num + k
) for the end boundary to properly implement the exclusive end of the range [num - k, num + k]
.
Wrong:
difference_array[num + k] -= 1 # This would exclude num + k from the range
Right:
difference_array[num + k + 1] -= 1 # Correctly includes num + k in the range
Which of these pictures shows the visit order of a depth-first search?
Recommended Readings
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
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Want a Structured Path to Master System Design Too? Don’t Miss This!