3346. Maximum Frequency of an Element After Performing Operations I
Problem Description
You have an integer array nums
and two integer parameters: 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 modified 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 modified 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 that any single element can achieve after performing all the operations optimally.
For example, if you have nums = [1, 2, 3]
, k = 1
, and numOperations = 2
, you could:
- Add 1 to
nums[0]
to make it 2 - Add -1 to
nums[2]
to make it 2 - This would give you
[2, 2, 2]
with a maximum frequency of 3
The key insight is that by adding values in the range [-k, k]
, you can potentially transform different elements to become equal, thereby increasing the frequency of a particular value. You need to strategically choose which elements to modify and what values to add to achieve the highest possible frequency for any element.
Intuition
The key observation is that for any target value x
, we can transform elements to become x
if they fall within the range [x - k, x + k]
. This is because we can add any value from [-k, k]
to an element.
Think about it this way: if we want to maximize the frequency of some value x
, we have two types of elements to consider:
- Elements that are already equal to
x
(they contribute to the frequency without using any operations) - Elements that can be transformed to
x
by adding a value in[-k, k]
(these require operations)
For each potential target value x
, the maximum frequency we can achieve is the sum of:
- Elements already equal to
x
(free contributions) - Up to
numOperations
elements from the range[x - k, x + k]
that we can transform tox
This leads us to think about counting, for each possible target value, how many elements fall within its "reachable range" [x - k, x + k]
.
The challenge is efficiently computing this for all possible target values. We can use a sweep line technique with difference arrays. For each element nums[i]
, it can be transformed to any value in the range [nums[i] - k, nums[i] + k]
. So nums[i]
contributes to the potential frequency of all values in this range.
By marking range boundaries using a difference array (d[x - k] += 1
and d[x + k + 1] -= 1
), we can efficiently compute how many elements can reach each target value. As we sweep through sorted positions, we maintain a running sum s
that tells us how many elements can be transformed to the current position.
The final answer for each position x
is min(s, cnt[x] + numOperations)
because:
s
is the total number of elements that can reachx
- But we can only modify
numOperations
elements - Elements already at
x
(counted incnt[x]
) don't need operations
Learn more about Binary Search, Prefix Sum, Sorting and Sliding Window patterns.
Solution Approach
The solution uses a sweep line algorithm with a difference array to efficiently count how many elements can reach each potential target value.
Step 1: Initialize Data Structures
cnt
: A dictionary to store the frequency of each original element innums
d
: A difference array to mark range boundaries for sweep line processing
Step 2: Process Each Element
For each element x
in nums
:
cnt[x] += 1
: Count the original frequency ofx
d[x] += 0
: Ensurex
exists as a key in the difference array (for elements already at this value)d[x - k] += 1
: Mark the start of the range wherex
can contribute (elements can be transformed fromx
to any value in[x - k, x + k]
)d[x + k + 1] -= 1
: Mark the end of the range (exclusive) using difference array technique
Step 3: Sweep Line Processing Sort the difference array by keys and process each position:
s += t
: Maintain a running sum that represents how many elements can be transformed to the current positionx
- For each position
x
, calculate the maximum achievable frequency:- Total elements that can reach
x
:s
- Elements already at
x
that don't need operations:cnt[x]
- Maximum frequency:
min(s, cnt[x] + numOperations)
- Total elements that can reach
The min
operation is crucial because:
- If
s <= cnt[x] + numOperations
: Alls
elements can contribute to the frequency - If
s > cnt[x] + numOperations
: We're limited by the number of operations, so we takecnt[x]
(free) plusnumOperations
(maximum we can transform)
Step 4: Return Maximum Track and return the maximum frequency found across all positions.
Time Complexity: O(n log n)
due to sorting the difference array
Space Complexity: O(n)
for storing the dictionaries
The elegance of this solution lies in using the difference array to avoid explicitly checking ranges for each potential target, reducing what could be an O(n²)
operation to O(n log n)
.
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, 5]
, k = 2
, and numOperations = 2
.
Step 1: Initialize Data Structures
cnt = {}
(frequency counter)d = {}
(difference array for sweep line)
Step 2: Process Each Element
For element 1
:
cnt[1] = 1
(one occurrence of 1)d[1] = 0
(ensure key exists)d[1-2] = d[-1] = 1
(start of range where 1 can contribute)d[1+2+1] = d[4] = -1
(end of range, exclusive)- Element 1 can be transformed to any value in [-1, 3]
For element 4
:
cnt[4] = 1
(one occurrence of 4)d[4] = 0
(ensure key exists, nowd[4] = -1 + 0 = -1
)d[4-2] = d[2] = 1
(start of range)d[4+2+1] = d[7] = -1
(end of range)- Element 4 can be transformed to any value in [2, 6]
For element 5
:
cnt[5] = 1
(one occurrence of 5)d[5] = 0
(ensure key exists)d[5-2] = d[3] = 1
(start of range)d[5+2+1] = d[8] = -1
(end of range)- Element 5 can be transformed to any value in [3, 7]
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]
Process each position with running sum s
:
Position | d[pos] | s (after adding) | cnt[pos] | min(s, cnt[pos] + numOps) | Explanation |
---|---|---|---|---|---|
-1 | 1 | 1 | 0 | min(1, 0+2) = 1 | Only element 1 can reach -1 |
1 | 0 | 1 | 1 | min(1, 1+2) = 1 | Element 1 is already here |
2 | 1 | 2 | 0 | min(2, 0+2) = 2 | Elements 1 and 4 can reach 2 |
3 | 1 | 3 | 0 | min(3, 0+2) = 2 | All 3 elements can reach 3, but limited by operations |
4 | -1 | 2 | 1 | min(2, 1+2) = 2 | Element 4 already here, element 5 can reach it |
5 | 0 | 2 | 1 | min(2, 1+2) = 2 | Element 5 already here, element 4 can reach it |
7 | -1 | 1 | 0 | min(1, 0+2) = 1 | Only element 5 can reach 7 |
8 | -1 | 0 | 0 | min(0, 0+2) = 0 | No elements can reach 8 |
Step 4: Return Maximum
The maximum frequency achieved is 2, which occurs at positions 2, 3, 4, and 5.
For example, at position 3:
- All three elements (1, 4, 5) can reach value 3
- Element 1 can add +2 to become 3
- Element 4 can add -1 to become 3
- Element 5 can add -2 to become 3
- But we can only perform 2 operations, so maximum frequency is 2
We could transform the array to [1, 3, 3] by modifying elements at indices 1 and 2, achieving a frequency of 2 for the value 3.
Solution Implementation
1from typing import List
2from collections import defaultdict
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 # Track range contributions using sweep line technique
10 # For each number x, we can change values in range [x-k, x+k] to become x
11 range_contributions = defaultdict(int)
12
13 for num in nums:
14 # Count original frequency
15 frequency_count[num] += 1
16
17 # Initialize the contribution at this number's position
18 range_contributions[num] += 0
19
20 # Mark the start of the range where this number can contribute
21 # (we can change num to any value in [num-k, num+k])
22 range_contributions[num - k] += 1
23
24 # Mark the end of the range (exclusive)
25 range_contributions[num + k + 1] -= 1
26
27 max_frequency = 0
28 current_sum = 0
29
30 # Process all positions in sorted order to calculate running sum
31 for position, contribution_delta in sorted(range_contributions.items()):
32 # Update running sum of how many numbers can be changed to this position
33 current_sum += contribution_delta
34
35 # Calculate max frequency at this position:
36 # - Original frequency: frequency_count[position]
37 # - Plus operations: min of available numbers and allowed operations
38 max_frequency = max(
39 max_frequency,
40 min(current_sum, frequency_count[position] + numOperations)
41 )
42
43 return max_frequency
44
1class Solution {
2 public int maxFrequency(int[] nums, int k, int numOperations) {
3 // Map to store the frequency of each number in the original array
4 Map<Integer, Integer> frequencyMap = new HashMap<>();
5
6 // TreeMap to track intervals where we can perform operations
7 // Uses difference array technique to efficiently track overlapping ranges
8 TreeMap<Integer, Integer> differenceArray = 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 // Mark the position of the original number (with 0 increment initially)
16 differenceArray.putIfAbsent(num, 0);
17
18 // Mark the start of the range [num - k, num + k] where we can change values to num
19 // Increment at start of range
20 differenceArray.merge(num - k, 1, Integer::sum);
21
22 // Mark the end of the range (exclusive) where we stop being able to change values to num
23 // Decrement after end of range
24 differenceArray.merge(num + k + 1, -1, Integer::sum);
25 }
26
27 int maxResult = 0;
28 int currentOverlappingRanges = 0; // Number of overlapping ranges at current position
29
30 // Traverse through all positions in sorted order
31 for (Map.Entry<Integer, Integer> entry : differenceArray.entrySet()) {
32 int position = entry.getKey();
33 int deltaValue = entry.getValue();
34
35 // Update the count of overlapping ranges at this position
36 currentOverlappingRanges += deltaValue;
37
38 // Calculate maximum frequency achievable at this position:
39 // - Original frequency of this value (if it exists in nums)
40 // - Plus up to numOperations changes from other values within range
41 // - Limited by the number of values that can be changed to this position
42 int achievableFrequency = Math.min(
43 currentOverlappingRanges,
44 frequencyMap.getOrDefault(position, 0) + numOperations
45 );
46
47 // Update the maximum frequency found so far
48 maxResult = Math.max(maxResult, achievableFrequency);
49 }
50
51 return maxResult;
52 }
53}
54
1class Solution {
2public:
3 int maxFrequency(vector<int>& nums, int k, int numOperations) {
4 // Map to store the frequency count of each number in nums
5 unordered_map<int, int> frequencyMap;
6
7 // Map to track range boundaries using difference array technique
8 // This helps count how many numbers fall within range [x-k, x+k] for each x
9 map<int, int> rangeDifference;
10
11 // Process each number in the input array
12 for (int num : nums) {
13 // Count frequency of current number
14 frequencyMap[num]++;
15
16 // Initialize the number as a potential target (even if count is 0)
17 rangeDifference[num];
18
19 // Mark range [num-k, num+k] using difference array
20 // Increment at start of range
21 rangeDifference[num - k]++;
22 // Decrement after end of range
23 rangeDifference[num + k + 1]--;
24 }
25
26 int maxResult = 0;
27 int currentRangeCount = 0; // Running sum for difference array
28
29 // Process each position in sorted order (map keeps keys sorted)
30 for (const auto& [position, difference] : rangeDifference) {
31 // Update running sum to get actual count at this position
32 currentRangeCount += difference;
33
34 // Calculate maximum frequency achievable at this position:
35 // - currentRangeCount: total numbers within k distance from position
36 // - frequencyMap[position] + numOperations: existing count plus operations
37 // Take minimum since we can't use more operations than available numbers in range
38 maxResult = max(maxResult, min(currentRangeCount, frequencyMap[position] + numOperations));
39 }
40
41 return maxResult;
42 }
43};
44
1/**
2 * Finds the maximum frequency of any element after performing at most numOperations
3 * operations, where each operation can change an element by at most k.
4 *
5 * @param nums - Array of integers
6 * @param k - Maximum change allowed per operation
7 * @param numOperations - Maximum number of operations allowed
8 * @returns Maximum possible frequency of any element
9 */
10function maxFrequency(nums: number[], k: number, numOperations: number): number {
11 // Count frequency of each number in the original array
12 const frequencyMap: Record<number, number> = {};
13
14 // Difference array to track range updates for possible target values
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 needed
23 differenceArray[num] = differenceArray[num] || 0;
24
25 // Mark the range [num - k, num + k] where this number can contribute
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 and running sum for sweep line
33 let maxFrequency: number = 0;
34 let runningSum: number = 0;
35
36 // Get all keys from difference array and sort them
37 const sortedKeys: number[] = Object.keys(differenceArray)
38 .map(Number)
39 .sort((a, b) => a - b);
40
41 // Sweep through all possible target values
42 for (const targetValue of sortedKeys) {
43 // Update running sum (number of elements that can reach this target)
44 runningSum += differenceArray[targetValue];
45
46 // Calculate maximum frequency at this target:
47 // - Elements already at this value (frequencyMap[targetValue])
48 // - Plus operations to convert other elements (limited by numOperations)
49 // - Total cannot exceed elements in range (runningSum)
50 const currentFrequency = Math.min(
51 runningSum,
52 (frequencyMap[targetValue] || 0) + numOperations
53 );
54
55 maxFrequency = Math.max(maxFrequency, currentFrequency);
56 }
57
58 return maxFrequency;
59}
60
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 areO(n)
unique keys - The final iteration through sorted items takes
O(n)
time - Overall time complexity is dominated by the sorting step:
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 - Overall space complexity is
O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Misunderstanding Range Boundaries
The Problem: A common mistake is incorrectly setting up the difference array boundaries. Developers often confuse:
- Whether to use
num - k
ornum + k
as the start of the range - Whether the end boundary should be
num + k
ornum + k + 1
Why This Happens: The confusion arises from mixing up two different perspectives:
- "What values can I change
num
into?" (Answer:[num - k, num + k]
) - "What values can be changed to reach position
x
?" (Answer: values in[x - k, x + k]
)
Incorrect Implementation:
# WRONG: Confusing the direction of transformation range_contributions[num + k] += 1 # Wrong start range_contributions[num - k + 1] -= 1 # Wrong end
Correct Solution:
# RIGHT: For each num, mark where it can contribute as a target range_contributions[num - k] += 1 # Start of range range_contributions[num + k + 1] -= 1 # End of range (exclusive)
Pitfall 2: Forgetting to Initialize Position Keys
The Problem: Failing to ensure that each original number's position exists in the difference array, leading to missed counting opportunities.
Why This Happens: The difference array only creates entries where boundaries exist. If a number doesn't serve as a boundary for any range, it won't appear in the sorted iteration.
Incorrect Implementation:
# WRONG: Not initializing positions for num in nums: frequency_count[num] += 1 # Missing: range_contributions[num] += 0 range_contributions[num - k] += 1 range_contributions[num + k + 1] -= 1
Correct Solution:
# RIGHT: Ensure every original number position is considered for num in nums: frequency_count[num] += 1 range_contributions[num] += 0 # Important: Initialize position range_contributions[num - k] += 1 range_contributions[num + k + 1] -= 1
Pitfall 3: Incorrect Maximum Frequency Calculation
The Problem: Misunderstanding how to combine the original frequency with the number of operations available.
Common Mistakes:
# WRONG 1: Always adding numOperations
max_frequency = frequency_count[position] + numOperations
# WRONG 2: Not considering original frequency
max_frequency = min(current_sum, numOperations)
# WRONG 3: Taking maximum instead of minimum
max_frequency = max(current_sum, frequency_count[position] + numOperations)
Correct Solution:
# RIGHT: Take minimum of available transformations and operation budget
max_frequency = max(
max_frequency,
min(current_sum, frequency_count[position] + numOperations)
)
The logic is:
current_sum
: Total elements that could potentially reach this positionfrequency_count[position] + numOperations
: Maximum we can actually achieve (original + transformed)- We take the minimum because we're constrained by either the available elements or our operation budget
Pitfall 4: Off-by-One Error in Difference Array
The Problem: Using inclusive end boundary instead of exclusive, causing incorrect counting.
Incorrect Implementation:
# WRONG: Using inclusive boundary range_contributions[num + k] -= 1 # Should be num + k + 1
This would exclude position num + k
from the range, even though we can transform num
to num + k
.
Correct Solution:
# RIGHT: Use exclusive end boundary range_contributions[num + k + 1] -= 1
This ensures the range [num - k, num + k]
is fully included in the sweep line calculation.
Is the following code DFS or BFS?
void search(Node root) { if (!root) return; visit(root); root.visited = true; for (Node node in root.adjacent) { if (!node.visited) { search(node); } } }
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!