涉及知识点
双指针
c++算法:前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频
贪心算法
题目leetcode: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 <= 105
1 <= nums[i] <= 109
0 <= k <= 1014
贪心算法(中位数贪心)
假定众数是x,假定nums的长度为n,将nums按升序排序。
x一定是nums中的数
我们用反证发证明。
x < nums[0] | 所有数先降到nums[0],再由nums[0]降到x,不如直接降到nums[0] |
x > nums[n-1] | 所有数先升到nums[n-1],再升到x,不如只升到nums[n-1] |
x在nums[i]和nums[j]之间,nums中比x小的a个数,比x大的b个数。 | 如果a>=b,x–,可以节省a-b个操作,直到x等于nums[i];否则x++,直到x等于nums[j]。 |
改变的数一定是一个子数组
假定改变的数是两个子数组[i1,i2]和[i3,i4]。如果x在[i1,i2]之间,则将i4替换成i2+1,直到两个子数组挨着一起合并。如果x在[i3,i4]之间,则i1替换i3-1,直到两个子数组挨着一起合并。
x只需要考虑中位数(中位数贪心算法)
来证明贪心算法的正确性。假定x是nums[i],x前面的数a个,x后面的数b个,i变成i-1操作次数变化:b-(a-1),如果表达式大于等于0,则没必要左移。b -a+1 >= 0,即a <=b+1。同理b <=a+1。即abs(a-b)<=1,则没必要左移和右移。
即:
如果n为偶数,中间任意一个。
如果n为奇数,中间的那个。
代码
核心代码
class solution {
public:
int maxfrequencyscore(vector<int>& nums, long long k) {
m_c = nums.size();
sort(nums.begin(), nums.end());
vector<long long> vpresum = { 0 };
for (const auto& n : nums)
{
vpresum.emplace_back(n+vpresum.back());
}
int iret = 0;
for (int left = 0, right = 0; left < m_c; left++)
{
while (right <= m_c)
{
const long long mid = left + (right - left) / 2;
const long long lllessneed = (mid - left) * nums[mid] - (vpresum[mid] - vpresum[left]);
const long long llequalmoreneed = (vpresum[right] - vpresum[mid]) - nums[mid] * (right - mid);
if (lllessneed + llequalmoreneed <= k)
{
iret = max(iret, right - left);
right++;
}
else
{
break;
}
}
}
return iret;
}
int m_c;
};
测试用例
void assert(const t& t1, const t& t2)
{
assert(t1 == t2);
}
template<class t>
void assert(const vector<t>& v1, const vector<t>& v2)
{
if (v1.size() != v2.size())
{
assert(false);
return;
}
for (int i = 0; i < v1.size(); i++)
{
assert(v1[i], v2[i]);
}
}
int main()
{
solution slu;
vector<int> nums;
int k;
{
solution slu;
nums = { 1,4,4,2,4 }, k = 0;
auto res = slu.maxfrequencyscore(nums, k);
assert(3, res);
}
{
solution slu;
nums = { 16, 2, 6, 20, 2, 18, 16, 8, 15, 19, 22, 29, 24, 2, 26, 19 }, k = 40;
auto res = slu.maxfrequencyscore(nums, k);
assert(11, res);
}
{
solution slu;
nums = { 1, 2, 6, 4 }, k = 3;
auto res = slu.maxfrequencyscore(nums, k);
assert(3, res);
}
//cconsole::out(res);
}
错误解法:二分查找+双指针
错误原因: 随着left增加targge可能减少
class solution {
public:
int maxfrequencyscore(vector& nums, long long k) {
m_c = nums.size();
sort(nums.begin(), nums.end());
vector vpresum = { 0 };
for (const auto& n : nums)
{
vpresum.emplace_back(n+vpresum.back());
}
long long llleftsum = 0;//nums[left,target)的和,nums升序
int iret = 0;
for (int left = 0, target = 0; left < m_c; left++)
{
while ((target < m_c) && (nums[target]*(target-left)- llleftsum <= k))
{
const int right = bf(vpresum,nums, target, k - (nums[target] * (target - left) - llleftsum));
iret = max(iret, right - left);
llleftsum += nums[target];
target++;
}
llleftsum -= nums[left];
}
return iret;
}
int bf(const vector& vpresum,const vector& nums, int index,long long canuse)
{
int left = index, right = vpresum.size();
while (right - left > 1)
{
const int mid = left + (right- left)/2 ;
if ((vpresum[mid] - vpresum[index]- nums[index] * (mid - index)) <= canuse)
{
left = mid;
}
else
{
right = mid;
}
}
return left;
}
int m_c;
};
扩展阅读
视频课程
有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步csdn学院,听白银讲师(也就是鄙人)的讲解。
如何你想快
速形成战斗了,为老板分忧,请学习c#入职培训、c++入职培训等课程
相关下载
想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
我想对大家说的话 |
---|
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
测试环境
操作系统:win7 开发环境: vs2019 c++17
或者 操作系统:win10 开发环境: vs2022 c++17
如无特殊说明,本算法用**c++**实现。
发表评论