有华为OD考试扣扣交流群可加:948025485
可上全网独家的 欧弟OJ系统 练习华子OD、大厂真题
绿色聊天软件戳
od1336
了解算法冲刺训练
文章目录
题目链接
题目描述
小扣当前位于魔塔游戏第一层,共有 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
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
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++
cpp
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
了解更多