LeetCode 2968. 执行操作使频率分数最大

2968. 执行操作使频率分数最大

给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。

你可以对数组执行 至多 k 次操作:

  • 从数组中选择一个下标 i ,将 nums[i] 增加 或者 减少 1 。

最终数组的频率分数定义为数组中众数的 频率 。

请你返回你可以得到的 最大 频率分数。

众数指的是数组中出现次数最多的数。一个元素的频率指的是数组中这个元素的出现次数。

示例 1:

输入:nums = [1,2,6,4], k = 3
输出:3
解释:我们可以对数组执行以下操作:
- 选择 i = 0 ,将 nums[0] 增加 1 。得到数组 [2,2,6,4] 。
- 选择 i = 3 ,将 nums[3] 减少 1 ,得到数组 [2,2,6,3] 。
- 选择 i = 3 ,将 nums[3] 减少 1 ,得到数组 [2,2,6,2] 。
元素 2 是最终数组中的众数,出现了 3 次,所以频率分数为 3 。
3 是所有可行方案里的最大频率分数。

示例 2:

输入:nums = [1,4,4,2,4], k = 0
输出:3
解释:我们无法执行任何操作,所以得到的频率分数是原数组中众数的频率 3 。

提示:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^9
  • 0 <= k <= 10^14

提示 1

If you sort the original array, it is optimal to apply the operations on one subarray such that all the elements of that subarray become equal.


提示 2

You can use binary search to find the longest subarray where we can make the elements equal in at most operations.

解法1:滑动窗口 + 前缀和

问题描述

给定一个整数数组nums和一个整数k,我们可以对数组中的任意元素执行至多k次操作,每次操作可以增加或减少该元素的值1。我们的目标是最大化数组中众数的频率,即出现次数最多的元素的个数。

问题讲解

将 nums 的所有元素变为 nums 的中位数是最优的。

证明:设 nums 的长度为 n,设要将所有 nums[i] 变为 x。假设 nums 已经从小到大排序。首先,如果 x 取在区间 [nums[0],nums[n−1]] 之外,那么 x 向区间方向移动可以使距离和变小;同时,如果 x 取在区间 [nums[0],nums[n−1]] 之内,无论如何移动 x,它到 nums[0] 和 nums[n−1] 的距离和都是一个定值 nums[n−1]−nums[0],那么去掉 nums[0] 和 nums[n−1] 这两个最左最右的数,问题规模缩小。不断缩小问题规模,如果最后剩下 1 个数,那么 x 就取它;如果最后剩下 2 个数,那么 x 取这两个数之间的任意值都可以(包括这两个数)。因此 x 可以取 nums[n/2]。

把数组排序后,要变成一样的数必然在一个连续子数组中,那么用滑动窗口来做,枚举子数组的右端点 right,然后维护子数组的左端点 left。

根据中位数贪心,最优做法是把子数组内的元素都变成子数组的中位数,操作次数如果超过 k,就必须移动左端点。

求出数组的前缀和,就可以 O(1) 算出操作次数了

解题思路

  1. 数组排序:首先,我们需要对数组nums进行排序。排序后的数组有助于我们确定操作的策略,因为最优解往往与数组的中位数有关。

  2. 中位数策略:排序后,最优的策略是将尽可能多的元素调整到中位数的值。因为中位数两边的元素向中位数调整所需的总变动次数是最小的。

  3. 滑动窗口:使用滑动窗口的方法,我们可以模拟从数组的一端到另一端,逐步确定哪些元素可以被调整,以及调整到哪个值。

  4. 前缀和:为了快速计算窗口内元素调整到特定值所需的总操作次数,我们使用前缀和数组来优化这个过程。

详细步骤

  1. 数组排序:对输入数组nums进行排序。

  2. 计算前缀和:构建一个前缀和数组presum,其中presum[i]表示从数组开始到索引i的元素的累积和。

  3. 初始化变量:设置left指针指向窗口的起始位置,ans用于记录最大频率分数。

  4. 遍历数组:遍历数组nums,使用right指针从0到n-1(数组长度减1)。

  5. 确定中位数:在每次迭代中,确定当前窗口的中位数。由于窗口是[left, right],中位数可以通过(left + right) // 2计算得出。

  6. 计算操作次数:计算将窗口内所有元素调整到中位数所需的操作次数。这可以通过计算窗口两端元素与中位数差值的和来实现。

  7. 更新窗口:如果所需的操作次数超过k,说明窗口太大,需要通过增加left指针的值来缩小窗口,然后重新计算操作次数。

  8. 更新最大频率:如果当前窗口的操作次数不超过k,则更新ans为窗口宽度(i - left + 1)的最大值。

  9. 返回结果:遍历结束后,返回ans作为最终结果。

Java版:

class Solution {
    public int maxFrequencyScore(int[] nums, long k) {
        Arrays.sort(nums);
        int n = nums.length;
        long[] presum = new long[n + 1];
        for (int i = 0; i < n; i++) {
            presum[i + 1] = presum[i] + nums[i];
        }
        int left = 0;
        int ans = 0;
        for (int i = 0; i < n; i++) {
            int mid = left + (i - left) / 2;
            long distanceSum = (long) nums[mid] * (mid - left) - (presum[mid] - presum[left]) + presum[i + 1] - presum[mid + 1] - (long) nums[mid] * (i - mid);
            while (left <= i && distanceSum > k) {
                left++;
                mid = left + (i - left) / 2;
                distanceSum = (long) nums[mid] * (mid - left) - (presum[mid] - presum[left]) + presum[i + 1] - presum[mid + 1] - (long) nums[mid] * (i - mid);
            }
            ans = Math.max(ans, i - left + 1);
        }
        return ans;
    }
}

Python3版:

class Solution:
    def maxFrequencyScore(self, nums: List[int], k: int) -> int:
        nums.sort()
        n = len(nums)
        presum = [0] * (n + 1)
        for i in range(n):
            presum[i + 1] = presum[i] + nums[i]
        ans = 0
        left = 0
        for i in range(n):
            mid = (i + left) // 2
            distanceSum = nums[mid] * (mid - left) - (presum[mid] - presum[left]) + presum[i + 1] - presum[mid + 1] - nums[mid] * (i - mid)
            while distanceSum > k:
                left += 1
                mid = (i + left) // 2
                distanceSum = nums[mid] * (mid - left) - (presum[mid] - presum[left]) + presum[i + 1] - presum[mid + 1] - nums[mid] * (i - mid)
            ans = max(ans, i - left + 1)
        return ans
复杂度分析
  • 时间复杂度:O(nlogn),其中 n 为 nums 的长度。瓶颈在排序上。
  • 空间复杂度:O(n)。
  • 12
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值