本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。
为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/LeetCode-Conquest。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。
由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。
你正在玩一款电子游戏,在游戏中你需要保护城市免受怪物侵袭。给你一个 下标从 0 开始 且长度为 n
的整数数组 dist
,其中 dist[i]
是第 i
个怪物与城市的 初始距离(单位:米)。
怪物以 恒定 的速度走向城市。给你一个长度为 n
的整数数组 speed
表示每个怪物的速度,其中 speed[i]
是第 i
个怪物的速度(单位:米/分)。
怪物从 第 0 分钟 时开始移动。你有一把武器,并可以 选择 在每一分钟的开始时使用,包括第 0 分钟。但是你无法在一分钟的中间使用武器。这种武器威力惊人,一次可以消灭任一还活着的怪物。
一旦任一怪物到达城市,你就输掉了这场游戏。如果某个怪物 恰 在某一分钟开始时到达城市,这会被视为 输掉 游戏,在你可以使用武器之前,游戏就会结束。
返回在你输掉游戏前可以消灭的怪物的 最大 数量。如果你可以在所有怪物到达城市前将它们全部消灭,返回 n
。
示例 1:
输入:dist = [1,3,4], speed = [1,1,1]
输出:3
解释:
第 0 分钟开始时,怪物的距离是 [1,3,4],你消灭了第一个怪物。
第 1 分钟开始时,怪物的距离是 [X,2,3],你没有消灭任何怪物。
第 2 分钟开始时,怪物的距离是 [X,1,2],你消灭了第二个怪物。
第 3 分钟开始时,怪物的距离是 [X,X,1],你消灭了第三个怪物。
所有 3 个怪物都可以被消灭。
示例 2:
输入:dist = [1,1,2,3], speed = [1,1,1,1]
输出:1
解释:
第 0 分钟开始时,怪物的距离是 [1,1,2,3],你消灭了第一个怪物。
第 1 分钟开始时,怪物的距离是 [X,0,1,2],你输掉了游戏。
你只能消灭 1 个怪物。
示例 3:
输入:dist = [3,2,4], speed = [5,3,2]
输出:1
解释:
第 0 分钟开始时,怪物的距离是 [3,2,4],你消灭了第一个怪物。
第 1 分钟开始时,怪物的距离是 [X,0,2],你输掉了游戏。
你只能消灭 1 个怪物。
提示:
n == dist.length == speed.length
1 <= n <= 10^5
1 <= dist[i], speed[i] <= 10^5
为了消灭尽可能多的怪物,我方需要坚持尽可能长的时间,因为我方每分钟都能消灭一个怪物。为了坚持更久,我方需要先消灭先来的怪物。因此,贪心的思路是将怪物的到达时间排序,先消灭到达时间早的怪物。我方的攻击时间序列是 [ 1 , 2 , 3 , … ] [1,2,3,\dots] [1,2,3,…] ,将「我方的攻击时间序列」和「排序后的怪物到达时间」依次进行比较,当第一次出现到达时间小于等于攻击时间,即表示怪物到达城市,我方会输掉游戏。
在比较时,因为我方的攻击时间为整数,因此可以将怪物到达时间向上取整,可以达到避免浮点数误差的效果。如果遍历完序列都没有出现这种情况,则表示我方可以消灭全部怪物。
class Solution {
public:
int eliminateMaximum(vector<int>& dist, vector<int>& speed) {
vector<int> a;
int n = dist.size();
for (int i = 0; i < n; ++i) a.push_back(i);
sort(a.begin(), a.end(), [&](const int &u, const int &v) {
double ma = 1.0 * dist[u] / speed[u];
double mb = 1.0 * dist[v] / speed[v];
return ma < mb;
});
int k = 0;
while (k < n && 1.0 * dist[a[k]] / speed[a[k]] > k) ++k;
return k;
}
};
复杂度分析:
着重讲解计数的思想:每只怪物都有一个最迟被解决的时间 T T T ,小于等于这个时间内,玩家可以随意选择一个时间去解决,而超过这个时间,怪物就会入侵城市,游戏也宣告失败。
计数数组 c o u n t count count ,就是用来记录每个最迟被解决的时间 T T T 上有多少个怪物。 T T T 的计算公式是 T = ( d i s t [ i ] − 1 ) / s p e e d [ i ] T=(dist[i] - 1) / speed[i] T=(dist[i]−1)/speed[i] 。当怪物的最迟被解决的时间 T T T 被计算出来后,对 c o u n t count count 进行 c o u n t [ T ] + + count[T]++ count[T]++ 操作。
有了计数数组 c o u n t count count 之后,我们就知道了在每个时刻,需要解决的怪物数量。这个时候,我们定义一个整数 k i l l kill kill 。表示我们在某个时间内能解决怪物的数量。如果在某个时刻我们能够解决怪物的数量 < 我们需要解决的怪物数量,说明怪物入侵城市,游戏失败。
k i l l kill kill 的计算规则也很简单,一分钟解决一只怪物,那么 t t t 时刻就能解决 t t t 只怪物。
注意: c o u n t count count 的大小如何确定呢?因为 T T T 的计算结果不确定,如果我们直接定义最大的数组涵盖所有情况,又会导致空间的浪费。实际上, c o u n t count count 的大小设定为 n n n 即可,因为怪物一共有 n n n 只,最迟 n n n 时刻内就能解决掉所有怪物,所以对于超过 n n n 的 T T T ,我们直接忽略就行。
class Solution {
public:
int eliminateMaximum(vector<int>& dist, vector<int>& speed) {
int n = dist.size();
vector<int> count(n, 0); //对每只怪物的最迟消灭时间进行计数
for(int i = 0; i < n; i++) {
int time = (dist[i] - 1) / speed[i]; //怪物需要在time时间内被消灭
if(time < n) //time >= n的怪物不用管
count[time]++;
}
int kill = 0; //能够击杀怪物的数量
for(int i = 0; i < n; i++) {
kill++; //每过一秒能多击杀一只怪物
kill -= count[i]; //减去限定时间需要击杀的怪物
if(kill < 0) //如果怪物到达城市
return i + 1;
}
return n;
}
};
复杂度分析: