2558. 从数量最多的堆取走礼物
1.题目介绍
给你一个整数数组 gifts ,表示各堆礼物的数量。每一秒,你需要执行以下操作:
选择礼物数量最多的那一堆。
如果不止一堆都符合礼物数量最多,从中选择任一堆即可。
选中的那一堆留下平方根数量的礼物(向下取整),取走其他的礼物。
返回在 k 秒后剩下的礼物数量。
示例 1:
输入:gifts = [25,64,9,4,100], k = 4
输出:29
解释:
按下述方式取走礼物:
- 在第一秒,选中最后一堆,剩下 10 个礼物。
- 接着第二秒选中第二堆礼物,剩下 8 个礼物。
- 然后选中第一堆礼物,剩下 5 个礼物。
- 最后,再次选中最后一堆礼物,剩下 3 个礼物。
最后剩下的礼物数量分别是 [5,8,9,4,3] ,所以,剩下礼物的总数量是 29 。
示例 2:
输入:gifts = [1,1,1,1], k = 4
输出:4
解释:
在本例中,不管选中哪一堆礼物,都必须剩下 1 个礼物。
也就是说,你无法获取任一堆中的礼物。
所以,剩下礼物的总数量是 4 。
提示:
1 <= gifts.length <= 103
1 <= gifts[i] <= 109
1 <= k <= 103
2.题解
具体关于堆的知识可参考:C++ 语法结构--堆
2.1 堆(使用优先级队列创建)
priority_queue 可以提供堆没有的优势,它可以自动保持元素的顺序;但我们不能打乱 priority_queue 的有序状态,因为除了第一个元素(top),我们无法直接访问它的其他元素。
使用一个最大堆维护各堆礼物的数量,弹出k次最大值,并进行平方根处理后,再重新插入最大堆。最后,最大堆中所有礼物的数量之和就是我们要返回的答案
class Solution {
public:
long long pickGifts(vector<int>& gifts, int k) {
priority_queue<int> p(gifts.begin(), gifts.end());
while (k--){
int num = p.top();
p.pop();
p.push(int(sqrt(num)));
}
// 求和
long long res = 0;
while (!p.empty()){
res += p.top();
p.pop();
}
return res;
}
};
2.2 堆(使用C++内置的make_heap函数)
思路
使用 make_heap()
创建的堆可以提供一些 priority_queue
没有的优势:
- 可以访问堆中的任意元素,而不限于最大的元素,因为元素被存储在一个容器中,就像是我们自己的
vector
。这也提供了偶然破坏元素顺序的可能,但是总可以调用make_heap()
来还原堆。 - 可以在任何提供随机访问迭代器的序列容器中创建堆。这些序列容器包括
普通数组
、string 对象
、自定义容器
。这意味着无论什么时候需要,都可以用这些序列容器的元素创建堆,必要时,可以反复创建。甚至还可以为元素的子集创建堆。
题解中的做法最后在求剩下元素之和的时候使用的是pop除所有元素,因此时间复杂度应该为\(O(n\cdot\text{log }n)\),而不是\(O(k\cdot\text{log }n)\)。为了不通过pop的方法弹出所有元素来遍历优先级队列,可以使用C++内置的make_heap
函数来处理:
代码
这里迭代器的使用类似于指针,我们使用反向迭代器 + *取值,获得pop_heap放在末尾的最大值,进行平方根操作后再重新push进堆中。
这里accumulate(gifts.begin(),gifts.end(),0ll); 中accumulate函数可以取一定范围内的数之和,前两个参数为迭代器,第三个参数为初始值,这里是(long long)0,省略写为0ll。
class Solution {
public:
long long pickGifts(vector<int>& gifts, int k) {
make_heap(gifts.begin(),gifts.end());
while (k--){
pop_heap(gifts.begin(),gifts.end());
*gifts.rbegin() = (int)(sqrt(*gifts.rbegin()));
push_heap(gifts.begin(),gifts.end());
}
return accumulate(gifts.begin(),gifts.end(),0ll);
}
};