LeetCode2558.从数量最多的堆取走礼物

LeetCode2558. 从数量最多的堆取走礼物【easy】。

题目描述

给你一个整数数组 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 <= 10^3
  • 1 <= gifts[i] <= 10^9
  • 1 <= k <= 10^3

题目分析

①方法一很简单,每次对gifts数组进行排序,最后一位更新为取平方根后剩余的礼物数量,循环k次之后计算新gifts数组里元素之和。排序开销较大,属于以时间换空间的办法;
②方法二较方法一复杂一些,但也很容易想到。对于一堆礼物,我们每次需要找到数量最大的值,并且更新完这个值后又放回这堆礼物中,继续寻找下一个最大值,这明显可以通过维护一个最大堆来实现,根节点的值永远都是当前这堆礼物的最大值。最后,最大堆中所有礼物的数量之和就是我们要返回的答案。最大堆需要额外的空间开销,属于以空间换时间的方式。在C#中,可以直接使用PriorityQueue,因为优先队列有一个强大的特点:自动排序,可以非常简单的实现我们的需求。需要注意的是,PriorityQueue<TElement,TPriority>类出队列时,将删除优先级值最低的项,所以越大的值应该设一个越小的优先级值,我们可以使用该值的负数来作为优先级值。

最大堆性质:结点的键值都小于等于其父结点的键值。
最小堆性质:结点的键值都大于等于其父结点的键值。
满足最大堆性质的二叉堆叫做最大堆,满足最小堆性质的二叉堆叫做最小堆。

题解

方法一

1
2
3
4
5
6
7
8
9
10
11
public long PickGifts(int[] gifts, int k) {
for(int i = 0; i < k; i++) {
Array.Sort(gifts);
gifts[gifts.Length - 1] = (int)Math.Floor(Math.Sqrt(gifts[gifts.Length - 1]));
}
long res = 0;
foreach(int i in gifts) {
res += (long)i;
}
return res;
}

方法二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public long PickGifts(int[] gifts, int k) {
PriorityQueue<int, int> pq = new PriorityQueue<int, int>();
foreach(int gift in gifts) {
pq.Enqueue(gift, -gift);
}
while(k > 0)
{
int x = pq.Dequeue();
Console.WriteLine(x);
pq.Enqueue((int)(Math.Floor(Math.Sqrt(x))), -(int)(Math.Floor(Math.Sqrt(x))));
k --;
}
long res = 0;
while(pq.Count > 0) {
res += pq.Dequeue();
}
return res;
}