1838. Frequency of The Most Frequent Element - Explanation

Problem Link

Description

The frequency of an element is the number of times it occurs in an array.

You are given an integer array nums and an integer k. In one operation, you can choose an index of nums and increment the element at that index by 1.

Return the maximum possible frequency of an element after performing at most k operations.

Example 1:

Input: nums = [1,2,4], k = 5

Output: 3

Explanation: Increment the first element three times and the second element two times to make nums = [4,4,4].
4 has a frequency of 3.

Example 2:

Input: nums = [1,4,8,13], k = 5

Output: 2

Explanation: There are multiple optimal solutions:

  • Increment the first element three times to make nums = [4,4,8,13]. 4 has a frequency of 2.
  • Increment the second element four times to make nums = [1,8,8,13]. 8 has a frequency of 2.
  • Increment the third element five times to make nums = [1,4,13,13]. 13 has a frequency of 2.

Constraints:

  • 1 <= nums.length <= 100,000
  • 1 <= nums[i], k <= 100,000


Topics

Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Sorting - Arranging elements to enable efficient window-based approaches
  • Sliding Window - Maintaining a dynamic window with adjustable boundaries
  • Prefix Sum - Efficiently computing range sums for cost calculations
  • Binary Search - Finding optimal boundaries in sorted data
  • Greedy Reasoning - Understanding why the target value must exist in the original array

1. Brute Force

Intuition

We can only increment elements, so the target value for our frequent element must already exist in the array. For each element, we check how many smaller elements we can increment to match it using at most k operations.

Sorting the array helps because the elements closest in value to our target require the fewest increments. We greedily extend leftward from each position, incrementing elements until we run out of operations.

Algorithm

  1. Sort the array in ascending order.
  2. For each index i, treat nums[i] as the target value.
  3. Starting from j = i - 1, work backward and subtract the cost nums[i] - nums[j] from a temporary budget.
  4. Stop when the budget becomes negative.
  5. The count of elements matching nums[i] is i - j.
  6. Track and return the maximum count found in res.
class Solution:
    def maxFrequency(self, nums: List[int], k: int) -> int:
        nums.sort()
        res = 1
        for i in range(len(nums)):
            j = i - 1
            tmpK = k
            while j >= 0 and (tmpK - (nums[i] - nums[j])) >= 0:
                tmpK -= (nums[i] - nums[j])
                j -= 1
            res = max(res, i - j)
        return res

Time & Space Complexity

  • Time complexity: O(n2+nlogn)O(n ^ 2 + n \log n)
  • Space complexity: O(1)O(1) or O(n)O(n) depending on the sorting algorithm.

Intuition

Instead of extending leftward one element at a time, we can use binary search to find the optimal left boundary. The cost to make all elements in a range equal to the rightmost element is: (count * target) - sum_of_range. Using prefix sums, we compute range sums in O(1).

For each right boundary i, we binary search for the smallest left boundary m such that the cost is within budget k. The window size i - m + 1 gives us the frequency.

Algorithm

  1. Sort the array and build a prefix sum array.
  2. For each index i:
    • Binary search for the leftmost index m where the cost (i - m + 1) * nums[i] - (prefix[i+1] - prefix[m]) is at most k.
    • Update res with the window size.
  3. Return the maximum frequency found.
class Solution:
    def maxFrequency(self, nums: List[int], k: int) -> int:
        nums.sort()
        n = len(nums)
        prefix_sum = [0] * (n + 1)
        for i in range(n):
            prefix_sum[i + 1] = prefix_sum[i] + nums[i]

        res = 1
        for i in range(n):
            l, r = 0, i
            while l <= r:
                m = (l + r) // 2
                curSum = prefix_sum[i + 1] - prefix_sum[m]
                need = (i - m + 1) * nums[i] - curSum
                if need <= k:
                    r = m - 1
                    res = max(res, i - m + 1)
                else:
                    l = m + 1
        return res

Time & Space Complexity

  • Time complexity: O(nlogn)O(n \log n)
  • Space complexity: O(n)O(n)

3. Sliding Window

Intuition

Since the array is sorted, we can use a sliding window. As we expand the window by moving the right pointer, we add elements and check if the cost to make all elements equal to the rightmost exceeds k. If it does, we shrink from the left.

The cost for a window is target * window_size - window_sum. When this exceeds k, we need a smaller window. The window always represents a valid subarray that can be made uniform within budget.

Algorithm

  1. Sort the array.
  2. Initialize l = 0, total = 0, and res = 0.
  3. For each right index r:
    • Add nums[r] to total.
    • While nums[r] * (r - l + 1) > total + k:
      • Subtract nums[l] from total and increment l.
    • Update res with r - l + 1.
  4. Return res.
class Solution:
    def maxFrequency(self, nums: List[int], k: int) -> int:
        nums.sort()
        total = res = 0
        l = 0

        for r in range(len(nums)):
            total += nums[r]
            while nums[r] * (r - l + 1) > total + k:
                total -= nums[l]
                l += 1
            res = max(res, r - l + 1)

        return res

Time & Space Complexity

  • Time complexity: O(nlogn)O(n \log n)
  • Space complexity: O(1)O(1) or O(n)O(n) depending on the sorting algorithm.

4. Advanced Sliding Window

Intuition

We can optimize further by observing that we only care about the maximum window size. Once we find a valid window of size w, we never need a smaller window. So instead of shrinking until valid, we can just slide the window forward, maintaining its size when invalid.

If a new element causes the window to become invalid, we remove exactly one element from the left, keeping the window size the same. The window size only grows when we find a valid configuration.

Algorithm

  1. Sort the array.
  2. Initialize l = 0 and total = 0.
  3. For each right index r:
    • Add nums[r] to total.
    • If (r - l + 1) * nums[r] > total + k:
      • Subtract nums[l] from total and increment l.
  4. The final window size is n - l which equals the maximum frequency.
  5. Return this value.
class Solution:
    def maxFrequency(self, nums: List[int], k: int) -> int:
        nums.sort()
        l = 0
        total = 0

        for r in range(len(nums)):
            total += nums[r]

            if (r - l + 1) * nums[r] > total + k:
                total -= nums[l]
                l += 1

        return len(nums) - l

Time & Space Complexity

  • Time complexity: O(nlogn)O(n \log n)
  • Space complexity: O(1)O(1) or O(n)O(n) depending on the sorting algorithm.

Common Pitfalls

Forgetting to Sort the Array

The sliding window and binary search approaches only work on a sorted array. Without sorting, elements that could be incremented to match a target value are scattered throughout the array, making it impossible to find contiguous windows efficiently. Always sort first before applying the window techniques.

Integer Overflow in Cost Calculation

When calculating the cost (window_size) * target - window_sum, the multiplication can overflow if using 32-bit integers. For large arrays with values up to 10^5, the product can exceed 2^31. Use long or long long for the total variable and cast appropriately before multiplication.

Incorrect Window Shrinking Condition

A common mistake is using >= instead of > when checking if the window is invalid. The condition should be nums[r] * (r - l + 1) > total + k (strictly greater), not >=. Using >= would shrink valid windows prematurely, potentially missing the optimal answer.