LCP30.魔塔游戏

小扣当前位于魔塔游戏第一层,共有 N 个房间,编号为 0 ~ N-1。每个房间的补血道具/怪物对于血量影响记于数组 nums,其中正数表示道具补血数值,即血量增加对应数值;负数表示怪物造成伤害值,即血量减少对应数值;0 表示房间对血量无影响。

小扣初始血量为 1,且无上限。假定小扣原计划按房间编号升序访问所有房间补血/打怪,为保证血量始终为正值,小扣需对房间访问顺序进行调整,每次仅能将一个怪物房间(负数的房间)调整至访问顺序末尾。请返回小扣最少需要调整几次,才能顺利访问所有房间。若调整顺序也无法访问完全部房间,请返回 -1。

示例 1:

输入: nums = [100,100,100,-250,-60,-140,-50,-50,100,150]
输出: 1
解释: 初始血量为 1。至少需要将 nums[3] 调整至访问顺序末尾以满足要求.

示例 2:

输入: nums = [-200,-300,400,0]
输出: -1
解释: 调整访问顺序也无法完成全部房间的访问。

提示:

  • 1 <= nums.length <= 10^5
  • -10^5 <= nums[i] <= 10^5

Related Topics

  • 贪心
  • 数组
  • 堆(优先队列)

题目链接: link

解答

本题的难度是 Medium.

这里的话我们可以考虑贪心算法, 从左到右遍历, 如果当前的血量小于 0 我们才考虑调整. 那这边我们当然是希望如果能够调整当然是能调整次数越少越好, 因此肯定选择把前面扣血最多的负数移到最后面, 因此其实我们相当于是需要维护一个最小堆, 因为只有负值才会放到堆里. 这样我们就可以每次取出堆顶的元素, 然后把血量加上这个值, 然后再把这个值放到最后面. 这样我们就可以得到最少需要调整的次数了. 在 Java 里面有一个小根堆的实现, 就是 PriorityQueue, 这个类默认是小根堆, 因此我们可以直接使用这个类.

代码如下:

class Solution {
    public int magicTower(int[] nums) {
        long temp =0;
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            temp += nums[i];
        }
        if (temp<0){return -1;}
        PriorityQueue<Integer> pq = new PriorityQueue<>();
        int result = 0;
        temp = 0;
        for (int i = 0; i < n; i++) {
            temp += nums[i];
            if (nums[i] < 0) {
                pq.offer(nums[i]);
            }
            if (temp < 0){
                temp -= pq.poll();
                result++;
            }
        }
        return result;
    }
}

时间开销为 10ms, 击败了 70% 的提交, 但是实际上很抽象啊, 我看了 9ms 的代码, 和我的基本一模一样. 这里有一些可能看起来会有疑虑的, 比如我 poll 之后其实就移除了这个元素, 难道不需要移到最后面吗?这里其实是因为我们一开始就判断了能不能成, 因为能成的话其实移到最后的负数是可以不用关心的, 因为只是结果是一定能达到的, 我们只需要适当调整过程就行了.

我看题解还有反悔贪心, 我一开始还奇怪什么叫反悔贪心, 原来就是先一直过过过过, 一个都不调整, 然后不行了, 这时候反悔, 我要调整了!然后调整还是按贪心, 要调整就调整能够影响最多的. 看起来和我的代码是一样的.

如果要大根堆的话, 稍微改变就行了

PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());