LeetCode 1921. Eliminate Maximum Number of Monsters【贪心,计数排序】1527

本文属于「征服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 贪心+排序

为了消灭尽可能多的怪物,我方需要坚持尽可能长的时间,因为我方每分钟都能消灭一个怪物。为了坚持更久,我方需要先消灭先来的怪物。因此,贪心的思路是将怪物的到达时间排序,先消灭到达时间早的怪物。我方的攻击时间序列是 [ 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;
    }
};

复杂度分析:

  • 时间复杂度: O ( n log ⁡ n ) O(n\log n) O(nlogn)
  • 空间复杂度: O ( n ) O(n) O(n)

解法2 贪心+计数排序

着重讲解计数的思想:每只怪物都有一个最迟被解决的时间 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;
    }
};

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n) ,其中 n n n 是数组 d i s t dist dist s p e e d speed speed 的长度。为两次遍历的时间复杂度。
  • 空间复杂度: O ( n ) O(n) O(n) 。需要一个数组保存怪物的到达时间。

你可能感兴趣的:(贪心,#,计数排序,leetcode,算法,职场和发展)