LeetCode 2126 - Destroying Asteroids
In this problem, we are given the starting mass of a planet and a list of asteroid masses. The planet can collide with the asteroids in any order we choose.
Difficulty: 🟡 Medium
Topics: Array, Greedy, Sorting
Solution
Problem Understanding
In this problem, we are given the starting mass of a planet and a list of asteroid masses. The planet can collide with the asteroids in any order we choose. Every time the planet collides with an asteroid, one of two things happens:
- If the planet's current mass is greater than or equal to the asteroid's mass, the asteroid is destroyed, and the planet gains that asteroid's mass.
- Otherwise, the planet is destroyed immediately, and the process fails.
The goal is to determine whether there exists some ordering of the asteroids such that the planet can destroy all of them successfully.
The important detail is that we are allowed to reorder the asteroids arbitrarily. This transforms the problem from a fixed simulation problem into an optimization problem where we must choose the best sequence of collisions.
The input consists of:
mass, the initial mass of the planet.asteroids, an array where each value represents the mass of one asteroid.
The output is a boolean:
trueif all asteroids can be destroyed.falseotherwise.
The constraints are large enough that efficiency matters:
- Up to
10^5asteroids - Each asteroid mass can also be up to
10^5
A quadratic solution would be too slow, so we need something around O(n log n) or better.
Several edge cases are important:
- A single asteroid larger than the initial mass.
- Many tiny asteroids that gradually increase the planet's mass.
- Duplicate asteroid masses.
- Already sorted or reverse sorted inputs.
- Cases where destroying almost every asteroid still leaves the planet too small for the final one.
Because the planet's mass continuously increases after successful collisions, the order of processing matters significantly.
Approaches
Brute Force Approach
The brute force idea is to try every possible ordering of the asteroids and simulate the collisions.
For each permutation:
- Start with the initial planet mass.
- Process asteroids in that order.
- If the planet can destroy every asteroid, return
true. - Otherwise, try another permutation.
This approach is correct because it explicitly checks all possible valid orders. If any successful order exists, brute force will eventually find it.
However, this approach is completely impractical for the given constraints. If there are n asteroids, there are n! possible orderings. Even for relatively small values of n, factorial growth becomes enormous.
For example:
10! = 3,628,80015!is already astronomically large
Since n can be 100,000, brute force is impossible.
Optimal Greedy Approach
The key observation is that the best strategy is always to destroy the smallest asteroid currently available.
Why does this work?
If the planet can destroy a small asteroid, it gains additional mass with minimal risk. Destroying smaller asteroids first maximizes the planet's growth before attempting larger asteroids.
Suppose we have two asteroids:
- Small asteroid:
a - Large asteroid:
b - where
a <= b
If the planet cannot destroy a, it definitely cannot destroy b.
If the planet can destroy b, it can also destroy a, and destroying a first only increases the planet's mass, making b easier afterward.
This means sorting the asteroids in ascending order is always optimal.
The algorithm becomes:
- Sort the asteroids.
- Iterate from smallest to largest.
- If the current asteroid is too large, return
false. - Otherwise, absorb it and continue.
This greedy strategy guarantees correctness.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n! × n) | O(n) | Tries every possible ordering |
| Optimal | O(n log n) | O(1) or O(log n) depending on sorting | Sort ascending and greedily absorb asteroids |
Algorithm Walkthrough
Step 1: Sort the asteroids
Sort the asteroids array in ascending order.
The reason for sorting is that smaller asteroids are always safer to destroy first. Every successful collision increases the planet's mass, which improves our chances against larger asteroids later.
For example:
asteroids = [3, 9, 19, 5, 21]
After sorting:
[3, 5, 9, 19, 21]
Step 2: Track the current planet mass
Initialize a variable to store the current planet mass.
Initially:
current_mass = mass
This value changes after every successful collision.
Step 3: Process asteroids from smallest to largest
Iterate through the sorted array.
For each asteroid:
- If
current_mass < asteroid, the planet cannot destroy it, so returnfalse. - Otherwise, add the asteroid mass to
current_mass.
This greedily maximizes future growth.
Step 4: Finish successfully
If the loop completes without failure, every asteroid was destroyed successfully.
Return true.
Why it works
The greedy strategy works because destroying smaller asteroids first can never hurt us.
If an asteroid cannot be destroyed now, then any larger asteroid also cannot be destroyed now. Conversely, if a smaller asteroid can be destroyed, absorbing it only increases the planet's mass and improves future possibilities.
Therefore, processing asteroids in ascending order always gives the maximum possible growth at every step. If this strategy fails, no alternative ordering could succeed.
Python Solution
from typing import List
class Solution:
def asteroidsDestroyed(self, mass: int, asteroids: List[int]) -> bool:
asteroids.sort()
current_mass = mass
for asteroid in asteroids:
if current_mass < asteroid:
return False
current_mass += asteroid
return True
The implementation begins by sorting the asteroid array in ascending order. This directly applies the greedy insight that smaller asteroids should always be handled first.
The variable current_mass stores the evolving mass of the planet. We initialize it with the given starting mass.
The loop iterates through every asteroid in sorted order. For each asteroid, we first check whether the planet is large enough to destroy it. If not, we immediately return False because no future ordering could succeed once the smallest remaining asteroid is already too large.
If the collision succeeds, the asteroid's mass is added to current_mass, simulating the absorption process.
If the loop finishes completely, then all asteroids were destroyed successfully, so the method returns True.
Go Solution
package main
import "sort"
func asteroidsDestroyed(mass int, asteroids []int) bool {
sort.Ints(asteroids)
currentMass := int64(mass)
for _, asteroid := range asteroids {
if currentMass < int64(asteroid) {
return false
}
currentMass += int64(asteroid)
}
return true
}
The Go implementation follows the same logic as the Python solution.
One important difference is the use of int64 for currentMass. While the constraints individually fit within standard integers, repeated additions can grow significantly larger than the initial values. Using int64 avoids overflow concerns during accumulation.
The built in sort.Ints() function efficiently sorts the slice in ascending order.
Go slices are naturally dynamic views over arrays, so no extra copying is needed for iteration.
Worked Examples
Example 1
Input:
mass = 10
asteroids = [3,9,19,5,21]
After sorting:
[3,5,9,19,21]
| Step | Current Mass | Asteroid | Can Destroy? | New Mass |
|---|---|---|---|---|
| 1 | 10 | 3 | Yes | 13 |
| 2 | 13 | 5 | Yes | 18 |
| 3 | 18 | 9 | Yes | 27 |
| 4 | 27 | 19 | Yes | 46 |
| 5 | 46 | 21 | Yes | 67 |
All asteroids are destroyed successfully.
Result:
true
Example 2
Input:
mass = 5
asteroids = [4,9,23,4]
After sorting:
[4,4,9,23]
| Step | Current Mass | Asteroid | Can Destroy? | New Mass |
|---|---|---|---|---|
| 1 | 5 | 4 | Yes | 9 |
| 2 | 9 | 4 | Yes | 13 |
| 3 | 13 | 9 | Yes | 22 |
| 4 | 22 | 23 | No | Failure |
The final asteroid is too large.
Result:
false
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n log n) | Sorting dominates the runtime |
| Space | O(1) or O(log n) | Depends on the sorting implementation |
The main cost comes from sorting the asteroid array. After sorting, the algorithm performs a single linear scan through the array.
The iteration itself is O(n), but sorting requires O(n log n), which dominates the total runtime.
The additional memory usage is minimal because the algorithm only stores a few variables besides the input array. The exact auxiliary space depends on the language's sorting implementation.
Test Cases
from typing import List
class Solution:
def asteroidsDestroyed(self, mass: int, asteroids: List[int]) -> bool:
asteroids.sort()
current_mass = mass
for asteroid in asteroids:
if current_mass < asteroid:
return False
current_mass += asteroid
return True
solution = Solution()
assert solution.asteroidsDestroyed(10, [3, 9, 19, 5, 21]) is True
# Provided example where all asteroids can be destroyed
assert solution.asteroidsDestroyed(5, [4, 9, 23, 4]) is False
# Provided example where final asteroid is too large
assert solution.asteroidsDestroyed(1, [1]) is True
# Smallest possible valid input
assert solution.asteroidsDestroyed(1, [2]) is False
# Single asteroid larger than initial mass
assert solution.asteroidsDestroyed(100, [1, 2, 3, 4]) is True
# Initial mass already exceeds every asteroid
assert solution.asteroidsDestroyed(3, [1, 2, 3]) is True
# Exact equality should still succeed
assert solution.asteroidsDestroyed(3, [4]) is False
# Immediate failure case
assert solution.asteroidsDestroyed(8, [1, 1, 1, 20]) is False
# Many small gains still insufficient
assert solution.asteroidsDestroyed(8, [1, 1, 1, 10]) is True
# Gradual accumulation eventually succeeds
assert solution.asteroidsDestroyed(10, [10, 10, 10]) is True
# Repeated equal values
assert solution.asteroidsDestroyed(2, [1, 3, 2]) is True
# Sorting is necessary for success
assert solution.asteroidsDestroyed(100000, [100000] * 100000) is True
# Large stress test with maximum values
| Test | Why |
|---|---|
mass=10, [3,9,19,5,21] |
Validates the standard successful scenario |
mass=5, [4,9,23,4] |
Validates failure after partial progress |
mass=1, [1] |
Smallest successful input |
mass=1, [2] |
Smallest failing input |
mass=100, [1,2,3,4] |
Planet starts overwhelmingly large |
mass=3, [1,2,3] |
Confirms equality is allowed |
mass=3, [4] |
Immediate impossible collision |
mass=8, [1,1,1,20] |
Gradual growth still insufficient |
mass=8, [1,1,1,10] |
Growth eventually enables success |
mass=10, [10,10,10] |
Handles duplicates and equal masses |
mass=2, [1,3,2] |
Demonstrates why sorting matters |
| Large maximum-value test | Stress tests performance and accumulation |
Edge Cases
One important edge case occurs when the planet's mass is exactly equal to an asteroid's mass. The problem statement says the planet succeeds when its mass is greater than or equal to the asteroid. A common bug is accidentally using a strict greater-than comparison. This implementation correctly uses < for failure detection, so equality succeeds properly.
Another important case is when the smallest asteroid is already too large. Since the array is sorted, if the first asteroid cannot be destroyed, no other ordering could possibly work. The implementation immediately returns False in this situation without unnecessary computation.
A third important edge case involves very large cumulative mass growth. Even though individual asteroid values are only up to 10^5, repeatedly adding them can create very large totals. In Go specifically, using standard int carelessly could risk overflow on some systems. The Go implementation avoids this by using int64 for the running mass total.
Another subtle edge case is when success depends entirely on choosing the correct order. For example:
mass = 2
asteroids = [1,3,2]
If processed in the given order:
2 -> destroy 1 -> mass 3
3 -> destroy 3 -> mass 6
6 -> destroy 2
Success occurs.
But an unfavorable order such as [3,1,2] would fail immediately. Sorting guarantees we always choose the safest growth strategy first.