​LCP 30. 魔塔游戏​

小扣当前位于魔塔游戏第一层,共有 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

解释:调整访问顺序也无法完成全部房间的访问。

来源:力扣(LeetCode)

链接:https://leetcode.cn/problems/p0NxJO

题解

贪心解法一

实际上就是在遍历累加的过程中计算总和,如果累加和小于0,则将0~当前索引位置中最小的那个负数放到末尾,此时应该将负数从累加和里面扣除。当遍历到数组末尾的时候,再将这些被移动到末尾的负数加回到累加中,如果总和大于等于0,则表示有解,否则无解。

步骤:

  1. 遍历整个数组,使用sum作为累加和
  2. 使用小根堆存放所有遇到的负数,小根堆的堆顶永远是最小的元素
  3. 如果sum小于等于0,则表示需要将某个怪物房间移动到末尾,此时根据贪心规则我们选取扣血量最大的,由于移动到了末尾,我们需要记录这个扣血量以便最后加回来,并且将堆顶元素弹出
  4. 遍历完数组后,再累加之前被移动到末尾的元素。我们使用原始数组来存放这些数,节省空间~~

代码如下:

  int magicTower2(vector<int> &nums)
{
long sum = 1;
int times = 0;

// 用一个小根堆存放遇到的所有负数
std::priority_queue<int, std::vector<int>, std::greater<int>> q;
for (const auto d : nums)
{
sum += d;
if (d < 0)
{
q.push(d);
}
if (sum <= 0)
{
if (!q.empty())// 将负数从小根堆中删除后移动到数组的开头,以记录是哪些负数需要被移动到末尾,方便最后累加回去
{
nums[times++] = q.top();
sum -= q.top();
q.pop();
}
}
}

for (int i = 0; i < times; ++i)
{
sum += nums[i];
}
return sum > 0 ? times : -1;
}

贪心解法二

在方法一中,我们通过一次遍历数组,以及将所有需要移动怪物房间的血量再加回去,比较符合人类的思维习惯。leetcode上还有另外一种解法,总体上和方法一一样,只不过先对数组求和判断是否有解。如果有解,再通过遍历数组累加,但是不再将要移动到末尾的怪物房间进行记录、累加。当遍历完数组的时候,就可以直接返回要移动的次数了。因为进入到第二次累加的时候,肯定是有解的,然后遍历完数组后,那些怪物房间不加回来也没关系啦。

2种方法没有优劣,只是思维略有不同。

注意:

  1. accumulate(nums.begin(), nums.end(), 0)计算的时候由于返回值是int,输入用例比较多的时候会导致整数溢出
  2. 根据经验,不要用int来接收累加和~~

代码如下:

  int magicTower(vector<int> &nums)
{
// int sum = accumulate(nums.begin(), nums.end(), 0);
long sum = 0;
std::for_each(nums.begin(), nums.end(), [&sum](int a)
{ sum += a; });
if (sum < 0)
{
return -1;
}
sum = 1;
int times = 0;
std::priority_queue<int, std::vector<int>, std::greater<int>> q;
for (const auto d : nums)
{
sum += d;
if (d < 0)
{
q.push(d);
}
if (sum <= 0)
{
if (!q.empty())
{
sum -= q.top();
q.pop();
times++;
}
}
}
return times;
}