1. 问题描述:
元素的频数是该元素在一个数组中出现的次数。给你一个整数数组 nums 和一个整数 k 。在一步操作中,你可以选择 nums 的一个下标,并将该下标对应元素的值增加 1 。执行最多 k 次操作后,返回数组中最高频元素的最大可能频数 。
示例 1:
输入:nums = [1,2,4], k = 5
输出:3
解释:对第一个元素执行 3 次递增操作,对第二个元素执 2 次递增操作,此时 nums = [4,4,4] 。
4 是数组中最高频元素,频数是 3 。
示例 2:
输入:nums = [1,4,8,13], k = 5
输出:2
解释:存在多种最优解决方案:
- 对第一个元素执行 3 次递增操作,此时 nums = [4,4,8,13] 。4 是数组中最高频元素,频数是 2 。
- 对第二个元素执行 4 次递增操作,此时 nums = [1,8,8,13] 。8 是数组中最高频元素,频数是 2 。
- 对第三个元素执行 5 次递增操作,此时 nums = [1,4,13,13] 。13 是数组中最高频元素,频数是 2 。
示例 3:
输入:nums = [3,9,6], k = 2
输出:1
提示:
1 <= nums.length <= 105
1 <= nums[i] <= 105
1 <= k <= 105
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/frequency-of-the-most-frequent-element
2. 思路分析:
① 分析题目可以知道我们需要在操作次数小于等于k的前提下求解出最高频元素的频数,所以最容易想到的是先对nums进行排序,排序之后对于nums的连续的片段操作次数是最小的。我们考虑将[l,r]范围的数字全部经过递增操作之后变为nums[r],那么操作的次数为:(r - l) * nums[r] + (num[l] + nums[l + 1] + .... + nums[r - 1]),实际上我们在操作的时候是可以先固定一个左边界l,然后扩展右边界r,经过排序之后往右边扩展那么操作次数肯定是增大的,因为操作次数最多是在k次,所以我们在扩展当前的右边界的时候发现当前的操作次数大于了k那么就需要将左边界右移,这样可以减少操作的次数。具体的操作是每一次都向右扩展一个长度,也即r到r + 1,这样扩展的话那么很容易就计算出了l-->r扩展之后的操作次数,上一次是l......r,当前是l......r + 1,所以r + 1边界比上一次增加的操作次数为(nums[r + 1] - nums[r]) * (r - l),实际上增加的就是(r - l)个数的相邻右边界差值,例如上一次的范围为[1, 2,3,4],扩展右边界一个长度之后[1,2,3,4,5],我们计算出(5 - 4)* (3 - 1)就是增加的操作次数。当我们扩展当前的右边界的时候那么可能次数是大于k的所以这个时候需要将左边界右移使得操作次数小于等于k,也即使用总的操作次数减去左边界到右边界的操作次数,即减去nums[r] - nums[l],循环进行左边界向右扩展操作的过程,当我们找出在操作次数小于等于k的时候r - l + 1就是当前范围的最高频元素的次数,这个时候根据历史上记录的最大值决定是否更新当前的最大值。其实本质上就是找到符合频数小于等于k的若干个区间,在这些区间中找到最长的那个,一开始的时候要清楚是滑动窗口的模型那么剩下来的就比较简单了,都是基本的套路,根据题目要求编写代码接即可。
② 除了使用滑动窗口的求解方法,我们还可以使用前缀和 + 二分查找的方法求解。对于排序好的nums数组,右边界为r,我们二分查找的是最左边的边界l使得操作次数在小于等于k,能够使用二分查找的原因是当我们找到一个左边界l,使得[l, r]范围的操作次数是小于等于k的,那么可能存在更小的左边界使得操作次数还是小于等于k的,越往左边走那么操作次数肯定是增大的,此时能够变为nums[r]的个数就更多,所以我们只需要枚举数组中每个元素对应位置的最左边的边界(右边界就为当前排序好的数组元素的位置),找到所有间隔中最大的那个就是答案。
3. 代码如下:
排序 + 滑动窗口:
from typing import List
class Solution:
def maxFrequency(self, nums: List[int], k: int) -> int:
nums.sort()
l = 0
total, res = 0, 1
for r in range(1, len(nums)):
# total记录总的操作次数
total += (nums[r] - nums[r - 1]) * (r - l)
# 有可能操作的次数是大于k的所以需要将左边界右移
while total > k:
total -= nums[r] - nums[l]
l += 1
# 左边界与右边界的差值加1就是最高频数元素的频数
res = max(res, r - l + 1)
return res
前缀和 + 二分:
from typing import List
class Solution:
def maxFrequency(self, nums: List[int], k: int) -> int:
n = len(nums)
s = [0] * (n + 1)
nums.sort()
# 计算前缀和
for i in range(1, n + 1):
s[i] = s[i - 1] + nums[i - 1]
res = 0
# 枚举每一个右边界r使用二分求解最左边的边界得到最大值即可
for i in range(1, n + 1):
# 变量t用来保存符合条件的左边界值
l, r, t = 1, i, 0
while l <= r:
mid = (l + r) // 2
if (i - mid + 1) * nums[i - 1] - (s[i] - s[mid - 1]) <= k:
t = mid
r = mid - 1
else:
l = mid + 1
res = max(res, i - t + 1)
return res