0%

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

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

题目描述:

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

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

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

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

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

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

数据范围:

$1\le nums.len \le 10^5, 1\le nums[i] \le 10^9, 0\le k \le 10^{14}$

题解:

首先还是需要排序,排序之后对一个子数组来说,将所有的数操作成一样的,最少的操作次数为将所有的数都改为中位数,和上一题一样。改成中位数之后,利用前缀和,可以快速计算出来花费。

考虑使用滑动窗口,每次检查窗口大小是否合适,不合适的话移动左指针,否则移动右指针。注意数据范围,不要越界。

image-20231218212458337

选择中位数的话,左侧代价为 $\sum x - x_i = cnt_i \times x - \sum x_i$ ,右侧代价为 $\sum x_j - x = \sum x_j - cnt_j \times x$ 。求和可以使用前缀和,这样的话可以 $O(1)$ 得到整个区间的代价。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
auto optimize_cpp_stdio = []()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
return 0;
}();
class Solution
{
public:
bool check(vector<int> &nums, vector<long long> &preSum, int l, int r, long long k)
{
// 从 1 开始
int x = l + (r - l >> 1);
long long cnt = preSum[r] - preSum[x] - 1ll * (r - x) * nums[x - 1] + 1ll * (x - l) * nums[x - 1] - (preSum[x - 1] - preSum[l - 1]);
return cnt <= k;
}
int maxFrequencyScore(vector<int> &nums, long long k)
{
int n = nums.size();
sort(nums.begin(), nums.end());
vector<long long> preSum(n + 1);
for (int i = 1; i <= n; ++i)
{
preSum[i] = preSum[i - 1] + nums[i - 1];
}
int ans = 1;
int l = 1, r = 1;
for (r = 1; r <= n; ++r)
{
while (!check(nums, preSum, l, r, k))
++l;
ans = max(ans, r - l + 1);
}
return ans;
}
};