【Py/Java/C++三种语言详解】LeetCode每日一题240206【反悔性贪心+堆】LCP30、魔塔游戏

37 篇文章 2 订阅
4 篇文章 0 订阅

有华为OD考试扣扣交流群可加:948025485
可上全网独家的 欧弟OJ系统 练习华子OD、大厂真题
绿色聊天软件戳 od1336了解算法冲刺训练

题目链接

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

解题思路

非常有意思的题目。

考虑人脑在处理这个问题的时候是如何做的。

假设我们正常地从头到尾遍历整个数组nums中的元素num,维护变量cur用于表示进入每一个房间之后的剩余血量。

当发现cur的血量降到小于等于0的时候,我们会想,如果在之前到达某个扣血扣得最多(绝对值最大的负数)的房间之前就事先把这个房间移动到末尾那就好了

换句话说,当血量小于等于0的时候,我们希望撤销之前扣得最多的那次扣血,使得我们现在的血量越高越高。

这就组成了一种带有反悔性质的贪心策略

那么应该如何找到在此之前扣血最多的那个房间呢?很容易想到用优先队列来维护这个过程。答案就呼之欲出了。

代码

Python

from heapq import heappop, heappush

# 贪心+优先队列:O(nlogn)
class Solution:
    def magicTower(self, nums: List[int]) -> int:
        ans = 0
        cur = 1
        # 优先队列储存已经访问过的负数
        visited = list()
        # 移动到末尾的负数的和
        remove_total = 0
        # 遍历所有数字
        for num in nums:
            # 将该数字加入cur中
            cur += num
            # 如果num是负数,则需要加入优先队列中
            if num < 0:
                heappush(visited, num)
            # 一旦发现cur降为0或0以下,则贪心地将前面访问过的最小的负数移动到数组末尾
            # 但并不需要显式地进行移动,只需要弹出visited堆顶元素remove_num即可
            # 同时cur回复之前扣除的血量,即减去remove_num
            # 由于remove_num被移动到末尾,将其加入remove_total中
            # 由于必须要做1次移动,因此更新ans
            if cur <= 0:
                remove_num = heappop(visited)
                cur -= remove_num        
                remove_total += remove_num
                ans += 1
        
        # 退出循环后,如果最后剩余血量cur加上被移动到末尾的血量remove_total仍然大于0
        # 则说明可以通过移动ans次房间,来顺利访问所有房间,
        # 否则返回-1,说明无论如何调整都无法访问所有房间,血量必然会扣为0
        return ans if cur + remove_total > 0 else -1

Java

class Solution {
    public int magicTower(int[] nums) {
        int ans = 0;
        long cur = 1;
        PriorityQueue<Integer> visited = new PriorityQueue<>(); // 小根堆

        long removeTotal = 0;
        for (int num : nums) {
            cur += num;
            if (num < 0) {
                visited.add(num);
            }
            if (cur <= 0) {
                int removeNum = visited.poll();
                cur -= removeNum;
                removeTotal += removeNum;
                ans++;
            }
        }

        return (cur + removeTotal > 0) ? ans : -1;
    }
}

C++

class Solution {
public:
    int magicTower(vector<int>& nums) {
        int ans = 0;
        long long cur = 1; // 使用long long 类型,防止溢出
        priority_queue<int, vector<int>, greater<int>> visited; // 小根堆

        long long remove_total = 0;
        for (int num : nums) {
            cur += num;
            if (num < 0) {
                visited.push(num);
            }
            if (cur <= 0) {
                int remove_num = visited.top();
                visited.pop();
                cur -= remove_num;
                remove_total += remove_num;
                ans++;
            }
        }

        return (cur + remove_total > 0) ? ans : -1;
    }
};

时空复杂度

时间复杂度:O(NlogN)。入堆操作的时间复杂度为O(logN)

空间复杂度:O(N)。堆所占空间。


华为OD算法/大厂面试高频题算法练习冲刺训练

  • 华为OD算法/大厂面试高频题算法冲刺训练目前开始常态化报名!目前已服务100+同学成功上岸!

  • 课程讲师为全网50w+粉丝编程博主@吴师兄学算法 以及小红书头部编程博主@闭着眼睛学数理化

  • 每期人数维持在20人内,保证能够最大限度地满足到每一个同学的需求,达到和1v1同样的学习效果!

  • 60+天陪伴式学习,40+直播课时,300+动画图解视频,300+LeetCode经典题,200+华为OD真题/大厂真题,还有简历修改、模拟面试、专属HR对接将为你解锁

  • 可上全网独家的欧弟OJ系统练习华子OD、大厂真题

  • 可查看链接 大厂真题汇总 & OD真题汇总(持续更新)

  • 绿色聊天软件戳 od1336了解更多

  • 19
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值