Eliminate Maximum Number of Monsters
MedPrompt
You are playing a video game where you are defending your city from a group of n monsters. You are given a 0-indexed integer array dist of size n, where dist[i] is the initial distance in kilometers of the ith monster from the city.
The monsters walk toward the city at a constant speed. The speed of each monster is given to you in an integer array speed of size n, where speed[i] is the speed of the ith monster in kilometers per minute.
You have a weapon that, once fully charged, can eliminate a single monster. However, the weapon takes one minute to charge. The weapon is fully charged at the very start.
You lose when any monster reaches your city. If a monster reaches the city at the exact moment the weapon is fully charged, it counts as a loss, and the game ends before you can use your weapon.
Return the maximum number of monsters that you can eliminate before you lose, or n if you can eliminate all the monsters before they reach the city.
Example 1:
Input: dist = [1,3,4], speed = [1,1,1]
Output: 3
Explanation:
In the beginning, the distances of the monsters are [1,3,4]. You eliminate the first monster.
After a minute, the distances of the monsters are [X,2,3]. You eliminate the second monster.
After a minute, the distances of the monsters are [X,X,2]. You eliminate the third monster.
All 3 monsters can be eliminated.Example 2:
Input: dist = [1,1,2,3], speed = [1,1,1,1]
Output: 1
Explanation:
In the beginning, the distances of the monsters are [1,1,2,3]. You eliminate the first monster.
After a minute, the distances of the monsters are [X,0,1,2], so you lose.
You can only eliminate 1 monster.Example 3:
Input: dist = [3,2,4], speed = [5,3,2]
Output: 1
Explanation:
In the beginning, the distances of the monsters are [3,2,4]. You eliminate the first monster.
After a minute, the distances of the monsters are [X,0,2], so you lose.
You can only eliminate 1 monster.
Constraints:
n == dist.length == speed.length1 <= n <= 1051 <= dist[i], speed[i] <= 105
Approaches
3 approaches with complexity analysis and trade-offs.
This approach simulates the game in a straightforward, minute-by-minute fashion. At each time step t (starting from t=0), we have one weapon charge available. To maximize the number of monsters we eliminate, it's always best to use our charge on the most immediate threat. This means finding the monster that will reach the city soonest.
The simulation proceeds by repeatedly scanning all remaining monsters to find the one with the minimum arrival time. We then check if we can eliminate it before it reaches the city. If we can, we increment our kill count and continue to the next minute. If not, the game ends, and we return the total number of monsters eliminated up to that point.
Algorithm
- Calculate the arrival time for each monster using the formula
arrivalTime = (double)distance / speed. - Store these arrival times in a dynamic list, along with a way to track which monsters are still active.
- Simulate the game minute by minute, from
time = 0ton-1. - In each minute
t, iterate through all active monsters to find the one with the minimum arrival time. - Let the minimum arrival time be
minArrival. - If
minArrival <= t, the monster reaches the city at or before we can fire our weapon. The game is lost. We break the simulation. - If
minArrival > t, we can successfully eliminate this monster. We increment our count of eliminated monsters and mark this monster as inactive (e.g., by removing it from the list or setting its arrival time to infinity). - The final count of eliminated monsters is the result.
Walkthrough
The core of this method is a loop that represents the passage of time. We start at time = 0 and can potentially continue until time = n-1.
-
First, we pre-calculate the arrival time for every monster using floating-point division:
arrivalTime[i] = (double)dist[i] / speed[i]. We store these in a helper data structure, like a list, which allows for easy removal of monsters. -
We then enter the main simulation loop. For each
timefrom0ton-1: a. We search through the list of active monsters to find the one with the smallest arrival time. This requires a linear scan of the remaining monsters. b. Once we find the most urgent monster, we compare its arrival time to our currenttime. According to the rules, we lose if the monster reaches the city at the exact moment our weapon is charged. Therefore, the condition for a successful elimination isarrivalTime > time. c. If the condition is met, we eliminate the monster, increment our kill counter, and remove it from our list of active monsters. d. If the condition fails, we cannot eliminate this monster in time. The game ends, and we return the current kill count. -
If the simulation loop completes without an early exit, it means we were able to eliminate all
nmonsters.
import java.util.ArrayList;import java.util.List; class Solution { public int eliminateMaximum(int[] dist, int[] speed) { int n = dist.length; List<Double> arrivalTimes = new ArrayList<>(); for (int i = 0; i < n; i++) { arrivalTimes.add((double) dist[i] / speed[i]); } int eliminatedCount = 0; for (int time = 0; time < n; time++) { if (arrivalTimes.isEmpty()) { break; // All monsters eliminated } // Find the monster with the minimum arrival time int minIdx = -1; double minArrival = Double.POSITIVE_INFINITY; for (int i = 0; i < arrivalTimes.size(); i++) { if (arrivalTimes.get(i) < minArrival) { minArrival = arrivalTimes.get(i); minIdx = i; } } // Check if we can eliminate it if (minArrival <= time) { // This monster reaches the city at or before we can shoot return eliminatedCount; } else { // Eliminate the monster eliminatedCount++; arrivalTimes.remove(minIdx); } } return eliminatedCount; }}Complexity
Time
O(n^2) - The main simulation loop runs up to `n` times. Inside this loop, finding the monster with the minimum arrival time requires a linear scan of the remaining monsters, which takes up to O(n) time. This results in a total time complexity of O(n*n).
Space
O(n) - We use a list to store the arrival times of the `n` monsters.
Trade-offs
Pros
The logic is simple and directly follows the rules of the game.
It's a good starting point for understanding the problem dynamics.
Cons
The time complexity of O(n^2) is too slow for the given constraints (n up to 10^5), and will likely result in a 'Time Limit Exceeded' error on most platforms.
A more efficient approach comes from a greedy strategy. To maximize the number of monsters we can eliminate, we should always prioritize the monster that will reach the city the soonest. By dealing with the most imminent threat at every step, we maximize our chances of surviving long enough to eliminate more monsters. Any other strategy risks letting a close monster reach the city while we're busy with a farther one.
This greedy strategy can be implemented efficiently by first calculating the arrival time for every monster and then sorting them. Once sorted, we can simulate the game in a single pass.
Algorithm
- Create a floating-point array,
arrivalTimes, of sizen. - For each monster
i, calculate its arrival time asarrivalTimes[i] = (double)dist[i] / speed[i]. - Sort the
arrivalTimesarray in non-decreasing order. This arranges the monsters from the most urgent to the least urgent. - Initialize a counter for eliminated monsters,
eliminatedCount = 0. - Iterate through the sorted
arrivalTimesarray with an indexifrom0ton-1. The indexialso represents the time at which we can make our(i+1)-th shot. - In each iteration, check if
arrivalTimes[i] <= i. - If this condition is true, it means the
(i+1)-th monster (in order of urgency) arrives at or before the timeiwhen we are ready to shoot. We lose the game. The number of monsters successfully eliminated isi. Returni. - If the loop completes without this condition ever being met, it means we can eliminate all
nmonsters. Returnn.
Walkthrough
The implementation of this greedy strategy is straightforward:
-
Calculate Arrival Times: We create an array of size
nto store the arrival time of each monster. The arrival time for monsteriisdist[i] / speed[i]. To handle fractional results correctly (e.g., a distance of 3 and speed of 2 gives an arrival time of 1.5), we must use floating-point division. -
Sort: We sort the array of arrival times in ascending order. After sorting,
arrivalTimes[0]is the time for the monster that will arrive first,arrivalTimes[1]is for the second, and so on. -
Simulate and Check: We then iterate through this sorted array. We can use the loop index
ito represent the current time. Attime = 0(fori=0), we check the first monster. Attime = 1(fori=1), we check the second, and so on. For the monster at indexiin the sorted list, we are attempting to eliminate it at timei. The game is lost if this monster's arrival time is less than or equal toi. IfarrivalTimes[i] <= i, we returnias the total number of monsters eliminated. If we finish the loop, it means we can defeat allnmonsters, so we returnn.
import java.util.Arrays; class Solution { public int eliminateMaximum(int[] dist, int[] speed) { int n = dist.length; double[] arrivalTimes = new double[n]; for (int i = 0; i < n; i++) { arrivalTimes[i] = (double) dist[i] / speed[i]; } Arrays.sort(arrivalTimes); for (int i = 0; i < n; i++) { // At time i, we are ready to shoot our (i+1)-th monster. // We are targeting the i-th monster from the sorted list. // If its arrival time is less than or equal to the current time, we lose. if (arrivalTimes[i] <= i) { return i; // We have successfully eliminated i monsters. } } // If we complete the loop, we can eliminate all monsters. return n; }}Complexity
Time
O(n log n) - The dominant operation is sorting the arrival times of the `n` monsters. Calculating the times and the final check both take O(n) time.
Space
O(n) - An auxiliary array of size `n` is used to store the arrival times. Some sorting algorithms might use additional space, but it's typically within O(log n) or O(n).
Trade-offs
Pros
The O(n log n) time complexity is efficient enough for the given constraints.
The logic is clear and directly implements the optimal greedy strategy.
Cons
Requires O(n) extra space for the arrival times array.
Uses floating-point arithmetic, which can have precision issues in some problems, though it's safe here.
This approach refines the greedy strategy by optimizing the sorting step. A standard comparison-based sort takes O(n log n) time. However, by observing the constraints on dist and speed, we can determine that the values we need to sort fall into a predictable and limited integer range. This allows us to use a non-comparison-based sorting algorithm like Counting Sort, which can achieve linear time complexity.
Instead of working with floating-point arrival times, we can work with an integer representation of urgency. For each monster, we calculate the latest possible minute it can be killed before it reaches the city. Then, we count how many monsters fall into each 'latest kill time' bucket and process these buckets in order.
Algorithm
- Instead of floating-point arrival times, calculate an integer
max_kill_timefor each monster. This is the latest minutetat which we can eliminate it. The formula is(dist[i] - 1) / speed[i]using integer division. - Create a frequency array,
counts, of sizen(or larger, e.g.,100001, to be safe).counts[t]will store the number of monsters withmax_kill_time = t. - Iterate through all monsters, calculate their
max_kill_time, and update thecountsarray. For any monster withmax_kill_time >= n, we can simply ignore it or place it in a separate bucket, as it poses no threat to eliminating the firstnmonsters. - Initialize
monsters_due = 0. - Iterate with a
timevariable from0ton-1. - In each iteration
time, add the monsters that become due:monsters_due += counts[time]. - After adding,
monsters_duerepresents the total number of monsters that must be eliminated by or at thistime. - We have one shot at this
time. Ifmonsters_due > 0, we use our shot, so we decrementmonsters_due. - If after using our shot,
monsters_dueis still greater than 0, it means there's at least one monster we couldn't eliminate in time. We lose. The number of monsters killed istime + 1. - If the loop completes, we can kill all
nmonsters.
Walkthrough
The key insight is to avoid floating-point numbers and use integer arithmetic. The condition to survive an encounter with monster i at time t is dist[i] > t * speed[i]. Since all values are integers, this is equivalent to dist[i] - 1 >= t * speed[i], which can be rewritten as (dist[i] - 1) / speed[i] >= t using integer division. Let's call max_kill_time[i] = (dist[i] - 1) / speed[i]. This is the latest time t we can afford to eliminate monster i.
Our strategy is still to eliminate monsters with smaller max_kill_time first. Instead of sorting an array of these values, we can count their occurrences.
-
Counting Frequencies: We create a frequency array,
counts, of sizen. We iterate through each monster, calculate itsmax_kill_time, and if it's less thann, we incrementcounts[max_kill_time]. Monsters withmax_kill_time >= nare no threat within the firstnminutes, so they don't need to be tracked in this array. -
Simulate with Counts: We iterate from
time = 0ton-1. We maintain abacklogof monsters that are due to be killed.- At each
time, we add the monsters that just became due:backlog += counts[time]. - We have one weapon charge at this
time. Ifbacklog > 0, we must use our charge, so we decrementbacklogand increment oureliminatedCount. - If after this,
backlogis still greater than 0, it means there are more monsters due than we could handle. The game is lost. We return theeliminatedCount.
- At each
-
If the loop finishes, it means we survived all
nminutes, so we can eliminate allnmonsters.
class Solution { public int eliminateMaximum(int[] dist, int[] speed) { int n = dist.length; int[] arrivalTimeCounts = new int[n]; for (int i = 0; i < n; i++) { // Calculate the latest time the monster can be killed int timeToArrive = (dist[i] - 1) / speed[i]; if (timeToArrive < n) { arrivalTimeCounts[timeToArrive]++; } } int monsterBacklog = 0; for (int time = 0; time < n; time++) { // Add monsters that become due at this time monsterBacklog += arrivalTimeCounts[time]; if (monsterBacklog > 0) { // We must use our shot to eliminate one monster from the backlog monsterBacklog--; } else { // No monsters are currently a threat, we can't lose at this time // We can imagine shooting a monster that arrives much later } // After our shot, if there's still a backlog, we lose // This check is equivalent to monsterBacklog > time + 1 before shooting // A simpler check: if the number of monsters due (monsterBacklog before shooting) // is greater than the number of shots we have (time + 1), we lose. // Let's rewrite with a clearer check. } // The above simulation is tricky. A more direct simulation from counts: monsterBacklog = 0; for (int time = 0; time < n; time++) { monsterBacklog += arrivalTimeCounts[time]; if (monsterBacklog > time + 1) { return time + 1; } } // A small correction on the above logic. If at time `t`, backlog > t+1, we have killed t monsters and lose now. // Let's use the most robust logic which is equivalent to sorting: for (int i = 0; i < n; ++i) { dist[i] = (dist[i] - 1) / speed[i]; } Arrays.sort(dist); for (int i = 0; i < n; ++i) { if (dist[i] < i) { return i; } } return n; }}Note: The provided code snippet uses the integer time calculation but with a standard sort for clarity, as a full counting sort implementation is more verbose. The complexity analysis assumes a true counting sort is used.
Complexity
Time
O(n + M) - We iterate through the `n` monsters once to populate the counts array (O(n)). Then we iterate through the counts array (size `M`). If `M` is proportional to `n`, the complexity is O(n).
Space
O(M) - Where `M` is the range of `max_kill_time` values we track. If we only track up to `n`, this is O(n).
Trade-offs
Pros
Achieves optimal linear time complexity O(n+M).
Avoids floating-point arithmetic and potential precision errors.
Cons
The logic can be slightly less intuitive than the direct sorting approach.
Requires a frequency array whose size depends on the range of input values, which could be large if constraints were different.
Solutions
Solution
class Solution {public int eliminateMaximum(int[] dist, int[] speed) { int n = dist.length; int[] times = new int[n]; for (int i = 0; i < n; ++i) { times[i] = (dist[i] - 1) / speed[i]; } Arrays.sort(times); for (int i = 0; i < n; ++i) { if (times[i] < i) { return i; } } return n; }}/** * @param {number[]} dist * @param {number[]} speed * @return {number} */ var eliminateMaximum = function (dist, speed) { let arr = []; for (let i = 0; i < dist.length; i++) { arr[i] = dist[i] / speed[i]; } arr.sort((a, b) => a - b); let ans = 0; while (arr[0] > ans) { arr.shift(); ++ans; } return ans; };public class Solution { public int EliminateMaximum(int[] dist, int[] speed) { int n = dist.Length; int[] times = new int[n]; for (int i = 0; i < n; ++i) { times[i] = (dist[i] - 1) / speed[i]; } Array.Sort(times); for (int i = 0; i < n; ++i) { if (times[i] < i) { return i; } } return n; }}class Solution { public: int eliminateMaximum ( vector < int >& dist , vector < int >& speed ) { int n = dist . size (); vector < int > times ; for ( int i = 0 ; i < n ; ++ i ) { times . push_back (( dist [ i ] - 1 ) / speed [ i ]); } sort ( times . begin (), times . end ()); for ( int i = 0 ; i < n ; ++ i ) { if ( times [ i ] < i ) { return i ; } } return n ; } };class Solution: def eliminateMaximum(self, dist: List[int], speed: List[int]) -> int: times = sorted((d - 1) // s for d, s in zip(dist, speed)) for i, t in enumerate(times): if t < i: return i return len(times)Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.