题目描述
小扣当前位于魔塔游戏第一层,共有 N 个房间,编号为 0 ~ N-1。每个房间的补血道具/怪物对于血量影响记于数组 nums,其中正数表示道具补血数值,即血量增加对应数值;负数表示怪物造成伤害值,即血量减少对应数值;0 表示房间对血量无影响。
小扣初始血量为 1,且无上限。假定小扣原计划按房间编号升序访问所有房间补血/打怪,为保证血量始终为正值,小扣需对房间访问顺序进行调整,每次仅能将一个怪物房间(负数的房间)调整至访问顺序末尾。请返回小扣最少需要调整几次,才能顺利访问所有房间。若调整顺序也无法访问完全部房间,请返回 -1。
题目示例
输入:nums = [100,100,100,-250,-60,-140,-50,-50,100,150]
输出:1
解释:初始血量为 1。至少需要将 nums[3] 调整至访问顺序末尾以满足要求。
解题思路
思路
我们按照原计划访问所有房间。当访问到第 i 个房间时,如果生命值小于等于 0,那么我们必须要对房间顺序进行调整:
显然选择第 i 个房间之后的房间是没有意义的,它并不会改变当前的生命值;
因此我们只能选择第 i 个房间及之前的房间。对于所有可选的房间,无论将哪个房间调整至末尾,都不会改变最终的生命值(因为数组 nums 的和不会变化)。由于我们希望调整的次数最少,因此应当贪心地选择最小的那个 nums[j] 调整至末尾,使得当前的生命值尽可能高。
算法
在遍历房间的过程中,如果 nums[i] 为负数,我们将其放入一个小根堆(优先队列)中。当计算完第 i 个房间的生命值影响后,如果生命值小于等于 0,那么我们取出堆顶元素,表示将该房间调整至末尾,并将其补回生命值中。由于一定会从小根堆中取出一个小于等于 nums[i] 的值,因此调整完成后,生命值一定大于 0。
当所有房间遍历完成后,我们还需要将所有从堆中取出元素的和重新加入生命值,如果生命值小于等于 0,说明无解。
参考代码
java
class Solution {
public int magicTower(int[] nums) {
// 建立小根堆,使得每次取都取最小值
PriorityQueue<Integer> pq = new PriorityQueue<>();
int ans = 0; // 记录调整次数
long hp = 1, delay = 0; // 记录当前血量 和 延迟掉血量
// 从头开始遍历
for(int i = 0; i < nums.length; i++) {
// 遇到正数,血量直接相加
if(nums[i] > 0) {
hp += nums[i];
// 遇到负数相加之后,血量相加,将负数血量放到小根堆
} else if(nums[i] < 0) {
hp += nums[i];
pq.offer(nums[i]);
// 看是否比0小,如果小于0,将之前最大的负数放到后面
if(hp <= 0) {
ans++;
int curr = pq.poll();
hp -= curr;
delay += curr;
}
}
}
hp += delay;
return hp <= 0 ? -1 : ans;
}
}