Maximum Frequency of an Element After Performing Operations II
HardPrompt
You are given an integer array nums and two integers k and numOperations.
You must perform an operation numOperations times on nums, where in each operation you:
- Select an index
ithat was not selected in any previous operations. - Add an integer in the range
[-k, k]tonums[i].
Return the maximum possible frequency of any element in nums after performing the operations.
Example 1:
Input: nums = [1,4,5], k = 1, numOperations = 2
Output: 2
Explanation:
We can achieve a maximum frequency of two by:
- Adding 0 to
nums[1], after whichnumsbecomes[1, 4, 5]. - Adding -1 to
nums[2], after whichnumsbecomes[1, 4, 4].
Example 2:
Input: nums = [5,11,20,20], k = 5, numOperations = 1
Output: 2
Explanation:
We can achieve a maximum frequency of two by:
- Adding 0 to
nums[1].
Constraints:
1 <= nums.length <= 1051 <= nums[i] <= 1090 <= k <= 1090 <= numOperations <= nums.length
Approaches
2 approaches with complexity analysis and trade-offs.
This approach involves systematically checking every possible contiguous subarray within the sorted nums array. For each subarray, we evaluate if all its elements can be unified into a single target value, given the constraints on operations. The core idea is that if a group of m elements can be made equal, they must form a subsequence where the difference between the maximum and minimum element is at most 2*k. By sorting the array, we can focus on contiguous subarrays as candidates for such groups. For each subarray, we then try to find a suitable target value T among its own elements that minimizes the number of required operations and satisfies all problem conditions.
Algorithm
- Sort the input array
nums. - Initialize a variable
maxFreqto 0, which will store the maximum possible frequency. - Iterate through all possible contiguous subarrays of
nums. This can be done with two nested loops, with outer loop variableifrom0toN-1and inner loop variablejfromitoN-1. - For each subarray
W = nums[i...j], check if it's possible to make all its elements equal to a single value. - A necessary condition is that
nums[j] - nums[i] <= 2*k. If this condition is violated, we can break the inner loop and advancei, as any further increase injwill also violate it. - If the condition holds, we need to find if there exists a target value
Tfor this subarray. The best candidates forTare the elements within the subarray itself, as this maximizes the number of elements that don't need to be changed. - We iterate through each unique element
xin the windownums[i...j]and test it as a potential targetT. - For each potential target
x, we first check if it's valid for the entire window:max(x - nums[i], nums[j] - x) <= k. - If
xis a valid target, we calculate its frequencycwithin the windownums[i...j]. - Let
m = j - i + 1be the size of the window. The number of operations required ism - c. We check if this is feasible with the given constraints:m - c <= numOperationsandnumOperations + c <= N(whereNis the total number of elements). - If both constraints are satisfied, it means a frequency of
mis achievable. We updatemaxFreq = max(maxFreq, m). - After checking all subarrays,
maxFreqwill hold the result.
Walkthrough
First, we sort the array nums to easily handle the condition max - min <= 2*k. After sorting, any subsequence that can be unified will be contained within a contiguous block nums[i...j] where nums[j] - nums[i] <= 2*k.
We use nested loops to consider every contiguous subarray nums[i...j]. For each subarray, we check if it's a candidate for unification. The length of this subarray, m = j - i + 1, is a potential frequency we can achieve.
To unify the elements of nums[i...j], we need a target T. A valid T must be reachable from every element in the subarray, meaning |T - nums[k]| <= k for all k from i to j. This is possible if and only if there's an overlap in the possible ranges, which simplifies to nums[j] - nums[i] <= 2*k.
Assuming this holds, we need to find a T that satisfies the operation constraints. The number of operations depends on how many elements are already equal to T. To minimize operations, we should choose a T that is already frequent in the subarray. The best candidates for T are the elements x present in nums[i...j]. For each such x, we check if it's a valid target for the whole subarray (max(x - nums[i], nums[j] - x) <= k). If it is, we count its occurrences c and check if the number of modifications m-c is within our budget (m-c <= numOperations) and if the leftover operations can be used on other elements (numOperations + c <= nums.length). If we find such a valid configuration, we update our maximum frequency found so far.
To implement this, we can use a frequency map for each window to count occurrences, leading to an overall O(N^2) complexity.
import java.util.Arrays;import java.util.HashMap; class Solution { public int maxFrequency(int[] nums, int k, int numOperations) { int n = nums.length; Arrays.sort(nums); int maxFreq = 0; // Find initial frequency for the case of 0 operations if (n > 0) { int currentStreak = 1; maxFreq = 1; for (int i = 1; i < n; i++) { if (nums[i] == nums[i - 1]) { currentStreak++; } else { currentStreak = 1; } maxFreq = Math.max(maxFreq, currentStreak); } } if (numOperations == 0) return maxFreq; for (int i = 0; i < n; i++) { HashMap<Integer, Integer> counts = new HashMap<>(); for (int j = i; j < n; j++) { if ((long)nums[j] - nums[i] > 2L * k) { break; } counts.put(nums[j], counts.getOrDefault(nums[j], 0) + 1); int m = j - i + 1; // Try each element in the window as a target for (int p = i; p <= j; p++) { int target = nums[p]; // Check if target is valid for the whole window if (Math.max((long)target - nums[i], (long)nums[j] - target) <= k) { int targetCount = counts.get(target); if (m - targetCount <= numOperations && numOperations + targetCount <= n) { maxFreq = Math.max(maxFreq, m); } } } } } return maxFreq; }}Complexity
Time
O(N^3) or O(N^2) with optimizations. Sorting takes O(N log N). The nested loops run in O(N^2). Inside the inner loop, iterating through the window to test targets takes O(N) time, leading to O(N^3). This can be optimized to O(N^2) by efficiently querying counts, but it's still too slow for the given constraints.
Space
O(N) in the worst case for the frequency map within the loops.
Trade-offs
Pros
It's a straightforward, brute-force-like approach that correctly explores the problem space based on the sorted array property.
It's easier to reason about and implement compared to more optimized and complex solutions.
Cons
The time complexity of O(N^2) can be too slow for the given constraints (N up to 10^5), likely resulting in a 'Time Limit Exceeded' error.
A more efficient approach is to change the perspective: instead of checking what frequency a subarray can achieve, let's fix a potential target value and calculate the maximum frequency we can achieve for it. The best candidates for a target value are the numbers already present in the array.
This approach iterates through each unique element x in the sorted nums array, treating it as the target T. For each T=x, we determine the maximum number of elements we can make equal to x. This number is determined by two factors: the number of operations available and the number of elements that are 'close enough' to x.
An element s is 'close enough' if it can be transformed into x by adding a value in [-k, k], which means s must be in the range [x-k, x+k]. In the sorted array, all such elements form a contiguous block. We can find this block efficiently using binary search.
The number of elements we can make equal to x is the sum of its original count (count_x) and the number of additional elements we can afford to change (numOperations). However, this is capped by the total number of 'close enough' elements we found. We also need to respect a more subtle constraint: the total number of operations must be performable on distinct indices, which leads to the condition numOperations + count_x <= nums.length.
Algorithm
- Sort the input array
nums. - Pre-calculate the frequency of each unique number in
numsand store it in a map. This can be done in O(N) on the sorted array. - Initialize
maxFreqto 0. IfnumOperationsis 0, the answer is simply the highest frequency of any element, which can be found during pre-calculation. - Iterate through each unique element
xinnums(e.g., by iterating through the keys of the frequency map). For eachx, consider it as the target valueT. - For a target
T=x, retrieve its original frequency,count_x, from the map. - Check the constraint
numOperations + count_x <= N. If this fails, it means we are forced to operate on elements we want to keep asx. In this case, the maximum frequency we can achieve forxis justcount_x. UpdatemaxFreq = max(maxFreq, count_x)and proceed to the next unique element. - If the constraint holds, find all elements in
numsthat can be converted tox. These are elementsswhere|x - s| <= k, which meanssmust be in the range[x - k, x + k]. - In the sorted
numsarray, these candidate elements form a contiguous block. Use binary search (lower_boundandupper_bound) to find the start indexland end indexrof this block. - The total number of candidates available to form the target
xisnumCandidates = r - l + 1. - With
numOperations, we can change up tonumOperationsother elements intox. So, the potential frequency iscount_x + numOperations. - This potential frequency is limited by the number of available candidates. Therefore, the maximum frequency achievable with
xas the target isachievableFreq = min(count_x + numOperations, numCandidates). - Update
maxFreq = max(maxFreq, achievableFreq). - After checking all unique elements as potential targets,
maxFreqwill hold the final answer.
Walkthrough
The algorithm begins by sorting the nums array in O(N log N) time. This allows for efficient searching and grouping of elements.
The core idea is to iterate through every unique number x present in nums and calculate the maximum frequency possible if x were the target value. For a chosen target x, we can form a group of m identical values by using its count_x original occurrences and converting m - count_x other numbers.
The number of operations required is m - count_x, which must be less than or equal to numOperations. This gives an upper bound on m: m <= count_x + numOperations.
The elements we convert must be transformable to x. An element s can be transformed to x if |s - x| <= k. All such convertible elements lie in the range [x-k, x+k]. Since nums is sorted, we can find the number of elements in this range in O(log N) time using two binary searches (one for the lower bound x-k and one for the upper bound x+k). Let this count be numCandidates.
The size of our final group m cannot be larger than numCandidates. Thus, we have another upper bound: m <= numCandidates.
Combining these, for a target x, the maximum frequency is min(count_x + numOperations, numCandidates). We also must satisfy the constraint that we have enough elements outside our target group to perform any leftover operations. This translates to numOperations + count_x <= N. If this is violated, we can only achieve a frequency of count_x.
We repeat this process for every unique element in nums and take the maximum frequency found. The overall time complexity will be O(N log N) because of the initial sort and the O(D log N) work in the loop, where D is the number of distinct elements (D <= N).
import java.util.Arrays; class Solution { public int maxFrequency(int[] nums, int k, int numOperations) { int n = nums.length; Arrays.sort(nums); int maxFreq = 0; if (n > 0) { int currentStreak = 0; for (int i = 0; i < n; i++) { if (i > 0 && nums[i] == nums[i-1]) { currentStreak++; } else { currentStreak = 1; } maxFreq = Math.max(maxFreq, currentStreak); } } int i = 0; while (i < n) { int j = i; while (j < n && nums[j] == nums[i]) { j++; } int count = j - i; long target = nums[i]; if (numOperations + count <= n) { long minVal = target - k; long maxVal = target + k; int startIdx = lowerBound(nums, minVal); int endIdx = upperBound(nums, maxVal); int numCandidates = endIdx - startIdx; int achievableFreq = Math.min(count + numOperations, numCandidates); maxFreq = Math.max(maxFreq, achievableFreq); } i = j; } return maxFreq; } // Finds the first index >= val private int lowerBound(int[] arr, long val) { int low = 0, high = arr.length; while (low < high) { int mid = low + (high - low) / 2; if (arr[mid] >= val) { high = mid; } else { low = mid + 1; } } return low; } // Finds the first index > val private int upperBound(int[] arr, long val) { int low = 0, high = arr.length; while (low < high) { int mid = low + (high - low) / 2; if (arr[mid] > val) { high = mid; } else { low = mid + 1; } } return low; }}Complexity
Time
O(N log N). Sorting takes O(N log N). The main loop iterates through unique elements. For each unique element, it performs two binary searches, taking O(log N). In the worst case, all elements are unique, leading to O(N log N) for the loop. The total complexity is dominated by these two parts.
Space
O(1) or O(log N) depending on the sort implementation's space usage. The algorithm itself uses constant extra space.
Trade-offs
Pros
Highly efficient with O(N log N) time complexity, which passes the given constraints.
The logic is sound and covers all aspects of the problem's constraints once understood.
Cons
The logic relies on a subtle understanding of the problem's constraints, particularly how operations are counted and distributed.
Requires careful implementation of binary search (or using library functions) to avoid off-by-one errors.
Solutions
Solution
class Solution {public int maxFrequency(int[] nums, int k, int numOperations) { Map<Integer, Integer> cnt = new HashMap<>(); TreeMap<Integer, Integer> d = new TreeMap<>(); for (int x : nums) { cnt.merge(x, 1, Integer : : sum); d.putIfAbsent(x, 0); d.merge(x - k, 1, Integer : : sum); d.merge(x + k + 1, -1, Integer : : sum); } int ans = 0, s = 0; for (var e : d.entrySet()) { int x = e.getKey(), t = e.getValue(); s += t; ans = Math.max(ans, Math.min(s, cnt.getOrDefault(x, 0) + numOperations)); } return ans; }}class Solution {public: int maxFrequency(vector<int> &nums, int k, int numOperations) { unordered_map<int, int> cnt; map<int, int> d; for (int x : nums) { cnt[x]++; d[x]; d[x - k]++; d[x + k + 1]--; } int ans = 0, s = 0; for (const auto &[x, t] : d) { s += t; ans = max(ans, min(s, cnt[x] + numOperations)); } return ans; }};class Solution: def maxFrequency(self, nums: List[int], k: int, numOperations: int) -> int: cnt = defaultdict(int) d = defaultdict(int) for x in nums: cnt[x] += 1 d[x] += 0 d[x - k] += 1 d[x + k + 1] -= 1 ans = s = 0 for x, t in sorted(d . items()): s += t ans = max(ans, min(s, cnt[x] + numOperations)) return ansVideo 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.