给你一个整数数组 piles
,数组 下标从 0 开始 ,其中 piles[i]
表示第 i
堆石子中的石子数量。另给你一个整数 k
,请你执行下述操作 恰好 k
次:
- 选出任一石子堆
piles[i]
,并从中 移除floor(piles[i] / 2)
颗石子。
注意:你可以对 同一堆 石子多次执行此操作。
返回执行 k
次操作后,剩下石子的 最小 总数。
floor(x)
为 小于 或 等于 x
的 最大 整数。(即,对 x
向下取整)。
示例 1:
输入:piles = [5,4,9], k = 2 输出:12 解释:可能的执行情景如下: - 对第 2 堆石子执行移除操作,石子分布情况变成 [5,4,5] 。 - 对第 0 堆石子执行移除操作,石子分布情况变成 [3,4,5] 。 剩下石子的总数为 12 。
示例 2:
输入:piles = [4,3,6,7], k = 3 输出:12 解释:可能的执行情景如下: - 对第 2 堆石子执行移除操作,石子分布情况变成 [4,3,3,7] 。 - 对第 3 堆石子执行移除操作,石子分布情况变成 [4,3,3,4] 。 - 对第 0 堆石子执行移除操作,石子分布情况变成 [2,3,3,4] 。 剩下石子的总数为 12 。
提示:
1 <= piles.length <= 10^5
1 <= piles[i] <= 10^4
1 <= k <= 10^5
提示 1
Choose the pile with the maximum number of stones each time.
提示 2
Use a data structure that helps you find the mentioned pile each time efficiently.
提示 3
One such data structure is a Priority Queue.
解法1:贪心 + 优先队列
思路
题目要求 k 次拿完后,剩下的石头数最少,可以贪心地每次都从石子数最多的堆里拿石头,然后将剩下的放回去。我们假设某一步操作没有选择石子数量最多的一堆(记为第 opt 堆)进行操作,而是选择了第 i 堆,那么后续可能有两种情况:
- 第一种,如果后续的某一步中选择了第 opt 堆,那么我们可以交换这两步的操作,最终剩余的石子数目不变;
- 第二种,如果后续没有移除过第 opt 堆的石子,那么将从当前开始到结束为止所有选择第 i 堆的操作全部换成第 opt 堆,总移除的石子数目也不会减少。
因此,每次选择石子数量最多的一堆进行操作是最优的。每次都要挑出石子数最多的堆,可以考虑用优先队列的数据结构,这样可以 O(logn) 的时间复杂度完成出队列和入队列,执行 k 次后返回剩下石子数总和。
Java版:
class Solution {
public int minStoneSum(int[] piles, int k) {
PriorityQueue<Integer> q = new PriorityQueue<Integer>((a, b) -> b - a);
for (int pile : piles) {
q.offer(pile);
}
while (k > 0) {
int x = q.poll();
x -= x / 2;
q.offer(x);
k--;
}
int ans = 0;
while (!q.isEmpty()) {
ans += q.poll();
}
return ans;
}
}
Python3版:
class Solution:
def minStoneSum(self, piles: List[int], k: int) -> int:
q = [-p for p in piles]
heapify(q)
while k > 0:
x = -heappop(q)
x -= x // 2
heappush(q, -x)
k -= 1
return -sum(q)
复杂度分析
- 时间复杂度:O(k×logn+n),其中 n 是数组 piles 的长度。将数组变为优先队列消耗 O(n),k 次入队列和出队列的操作,每次消耗 O(logn),总的时间复杂度是 O(k×logn+n)。
- 空间复杂度:O(n),新建一个优先队列消耗 O(n)。