LeetCode100123执行操作使频率分数最大(相关话题:滑动窗口,二分法,前缀和)

题目描述

给你一个下标从 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 <= 105
  • 1 <= nums[i] <= 109
  • 0 <= k <= 1014

解题思路

先看一下滑动窗口的代码

class Solution:
    def maxFrequencyScore(self, nums: List[int], k: int) -> int:
        nums.sort()  # 排序数组
        max_freq = 1  # 至少有一个数的频率
        l = 0  # 窗口的左边界
        total = 0  # 窗口内所有数变为窗口内最大数所需的总操作次数

        # r为窗口的右边界
        for r in range(len(nums)):
            # 将nums[r]加入窗口
            total += nums[r]

            # 如果窗口内所有数变为nums[r]所需的操作次数超过k
            # 则移动左边界,直到操作次数不超过k
            while nums[r] * (r - l + 1) > total + k:
                total -= nums[l]
                l += 1

            # 更新最大频率
            max_freq = max(max_freq, r - l + 1)
  • 每次以nums[right]作为变化的基准数不是最优策略可以优化为二分查找中位数
  1. 假定所有的 nums[i]均位于数轴上的 nums[i]的位置,题目要求我们在数轴上找出一个点 t,使得所有 nums[i]到 t 的距离之和最小
  2. 首先,容易证明 t 不可能位于最小的nums[i]的左侧,也不可能位于最大的 nums[i]的右侧,否则我们「至少」能够将目标点调整为 最小的 nums[i]或 最大的 nums[i] 来得到更小的距离总和。
  3.  t 取中位数时,距离之和最小。可以通过「反证法」证明(略)
  • total用累加的方式不是最优的方案,数据量大时会超时可以优化为前缀和
class Solution:
    def maxFrequencyScore(self, nums: List[int], k: int) -> int:
            nums.sort()
            left = 0
            res = 0
            n = len(nums)

            # 计算前缀和
            prefix_sum = [0]
            for num in nums:
                prefix_sum.append(prefix_sum[-1] + num)

            def calculateCost(mid, left, right):
                #cost_left 计算的是将下标范围 [left, mid] 内的元素变为 nums[mid] 所需的操作次数。
                cost_left = nums[mid] * (mid - left + 1) - (prefix_sum[mid+1] - prefix_sum[left])
                #cost_right 计算的是将下标范围 [mid + 1, right] 内的元素变为 nums[mid] 所需的操作次数。
                cost_right = (prefix_sum[right + 1] - prefix_sum[mid + 1]) - nums[mid] * (right - mid)
                return cost_left + cost_right

            for right in range(n):
                mid = (left + right) // 2

                # 当前窗口内将所有元素变为 nums[mid] 需要的操作次数
                while calculateCost(mid, left, right) > k:
                    # 如果操作次数超过 k,则移动窗口左边界
                    left += 1
                    mid = (left + right) // 2

                # 更新最大频率
                res = max(res, right - left + 1)

            return res

几个需要记住的结论: 

  • 构造的前缀和数组prefix_sum长度比nums长度大1,第一位固定为0(prefix_sum[0]==0)
  • nums[i]到num[j]的和可以表示为prefix_sum[[j+1]-prefix_sum[[i]
  • prefix_sum[i] = prefix_sum[i-1] + nums[i-1]

中位数贪心

右边数字为难度分

先刷462再刷2448

class Solution:
    def minCost(self, nums: List[int], cost: List[int]) -> int:
        a = sorted(zip(nums, cost))
        #cost可以理解为cost个nums[i]这样问题就转化为了中心数贪心问题
        s, mid = 0, (sum(cost) + 1) // 2
        for x, c in a:
            s += c
            if s >= mid:
                return sum(abs(y - x) * c for y, c in a)  # 把所有数变成 x

 
  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

数据与后端架构提升之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值