1838 最高频元素的频数(排序 + 滑动窗口、前缀和 + 二分查找)

41 篇文章 0 订阅
13 篇文章 0 订阅

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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值