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 = nums[mid] * (mid - left) - (prefix_sum[mid] - prefix_sum[left])
                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

中位数贪心

右边数字为难度分

先刷462再刷2448

  • 462. 最小操作次数使数组元素相等 II
  • 2033. 获取单值网格的最小操作数 1672
  • 2448. 使数组相等的最小开销 2005
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

 
  • 2607. 使子数组元素和相等 2071
  • 1703. 得到连续 K 个 1 的最少相邻交换次数 2467

你可能感兴趣的:(算法,算法,数据结构,leetcode)