2126. Destroying Asteroids
Problem Description
You have a planet with an initial mass and a collection of asteroids with different masses. Your goal is to determine if the planet can destroy all asteroids by colliding with them.
The rules are:
- You can choose to collide with asteroids in any order you want
- When the planet collides with an asteroid:
- If the planet's mass is greater than or equal to the asteroid's mass: the asteroid is destroyed and the planet absorbs its mass (planet's mass increases by the asteroid's mass)
- If the planet's mass is less than the asteroid's mass: the planet is destroyed
Given:
mass
: an integer representing the initial mass of the planetasteroids
: an array of integers whereasteroids[i]
represents the mass of the i-th asteroid
Return true
if it's possible to destroy all asteroids, false
otherwise.
The key insight is that since you can choose the collision order, you should collide with smaller asteroids first to grow the planet's mass before tackling larger ones. This is why the solution sorts the asteroids in ascending order and greedily checks if each asteroid can be destroyed. If at any point the planet's mass is less than the current asteroid's mass, the planet would be destroyed and we return false
. If we successfully destroy all asteroids, we return true
.
Intuition
The key observation is that we have complete freedom to choose the order in which we collide with asteroids. This means we should strategically pick an order that maximizes our chances of success.
Think about it this way: if we encounter a large asteroid early when our planet is still small, we might get destroyed. But if we first collide with smaller asteroids to grow our mass, we might be able to handle that same large asteroid later.
This naturally leads to a greedy strategy: always collide with the smallest available asteroid first. Why does this work?
-
If we can't destroy the smallest asteroid (our mass is less than it), then we definitely can't destroy any larger asteroids either. The game is over.
-
If we can destroy the smallest asteroid, we should do it immediately because:
- It grows our mass, making us stronger for future collisions
- There's no benefit to delaying this collision - we'll need to destroy it eventually anyway
- By growing our mass as early as possible, we maximize our chances of handling larger asteroids later
This is why sorting the asteroids in ascending order and processing them one by one works perfectly. At each step, we're facing the smallest remaining asteroid. If we can't handle it, we know it's impossible to destroy all asteroids. If we can handle it, we absorb its mass and move on to the next smallest asteroid.
The beauty of this approach is that it's both optimal and simple - by always choosing the easiest target first and growing stronger, we give ourselves the best chance of completing the mission.
Solution Approach
The implementation follows a straightforward sorting + greedy strategy:
-
Sort the asteroids array in ascending order: We use
asteroids.sort()
to arrange all asteroids from smallest to largest mass. This ensures we always encounter asteroids in the optimal order. -
Iterate through the sorted asteroids: We process each asteroid one by one using a simple for loop:
for x in asteroids:
-
Check if the planet can destroy the current asteroid: At each step, we compare the planet's current mass with the asteroid's mass:
- If
mass < x
: The planet cannot destroy this asteroid and will be destroyed instead. We immediately returnFalse
since it's impossible to destroy all asteroids. - If
mass >= x
: The planet successfully destroys the asteroid.
- If
-
Update the planet's mass: When an asteroid is destroyed, the planet absorbs its mass:
mass += x
. This makes the planet stronger for subsequent collisions. -
Return the final result: If we successfully iterate through all asteroids without returning
False
, it means all asteroids have been destroyed, so we returnTrue
.
The algorithm has a time complexity of O(n log n)
due to the sorting step, where n
is the number of asteroids. The space complexity is O(1)
if we don't count the sorting space (or O(n)
if we do, depending on the sorting algorithm used).
The elegance of this solution lies in its simplicity - by sorting first and then greedily consuming asteroids from smallest to largest, we guarantee the optimal outcome with minimal code.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through an example where we have a planet with initial mass 5
and asteroids with masses [4, 9, 23, 4, 1]
.
Step 1: Sort the asteroids in ascending order
- Original:
[4, 9, 23, 4, 1]
- Sorted:
[1, 4, 4, 9, 23]
Step 2: Process each asteroid from smallest to largest
Collision 1: Planet (mass=5) vs Asteroid (mass=1)
- Since 5 โฅ 1, the asteroid is destroyed
- Planet absorbs the mass: 5 + 1 = 6
- New planet mass: 6
Collision 2: Planet (mass=6) vs Asteroid (mass=4)
- Since 6 โฅ 4, the asteroid is destroyed
- Planet absorbs the mass: 6 + 4 = 10
- New planet mass: 10
Collision 3: Planet (mass=10) vs Asteroid (mass=4)
- Since 10 โฅ 4, the asteroid is destroyed
- Planet absorbs the mass: 10 + 4 = 14
- New planet mass: 14
Collision 4: Planet (mass=14) vs Asteroid (mass=9)
- Since 14 โฅ 9, the asteroid is destroyed
- Planet absorbs the mass: 14 + 9 = 23
- New planet mass: 23
Collision 5: Planet (mass=23) vs Asteroid (mass=23)
- Since 23 โฅ 23, the asteroid is destroyed
- Planet absorbs the mass: 23 + 23 = 46
- Final planet mass: 46
Result: All asteroids destroyed successfully, return True
Notice how by tackling the smaller asteroids first, we built up enough mass to handle the larger ones. If we had tried to collide with the asteroid of mass 23 first (when our planet only had mass 5), we would have been destroyed immediately!
Solution Implementation
1class Solution:
2 def asteroidsDestroyed(self, mass: int, asteroids: List[int]) -> bool:
3 """
4 Determine if a planet can destroy all asteroids by absorbing their mass.
5
6 The planet starts with initial mass and can destroy asteroids with mass <= planet's current mass.
7 After destroying an asteroid, the planet absorbs its mass.
8
9 Args:
10 mass: Initial mass of the planet
11 asteroids: List of asteroid masses
12
13 Returns:
14 True if all asteroids can be destroyed, False otherwise
15 """
16 # Sort asteroids in ascending order to tackle smaller ones first
17 # This greedy approach maximizes the planet's growth potential
18 asteroids.sort()
19
20 # Iterate through each asteroid in ascending order
21 for asteroid_mass in asteroids:
22 # Check if current planet mass is sufficient to destroy this asteroid
23 if mass < asteroid_mass:
24 # Planet cannot destroy this asteroid, mission fails
25 return False
26
27 # Absorb the destroyed asteroid's mass into the planet
28 mass += asteroid_mass
29
30 # All asteroids successfully destroyed
31 return True
32
1class Solution {
2 /**
3 * Determines if a planet can destroy all asteroids by absorbing their mass.
4 * The planet can only destroy asteroids with mass less than or equal to its current mass.
5 * After destroying an asteroid, the planet's mass increases by the asteroid's mass.
6 *
7 * @param mass Initial mass of the planet
8 * @param asteroids Array of asteroid masses to be destroyed
9 * @return true if all asteroids can be destroyed, false otherwise
10 */
11 public boolean asteroidsDestroyed(int mass, int[] asteroids) {
12 // Sort asteroids in ascending order to tackle smaller ones first
13 Arrays.sort(asteroids);
14
15 // Use long to prevent integer overflow as mass accumulates
16 long currentMass = mass;
17
18 // Iterate through each asteroid in sorted order
19 for (int asteroidMass : asteroids) {
20 // Check if current mass is sufficient to destroy this asteroid
21 if (currentMass < asteroidMass) {
22 return false; // Cannot destroy this asteroid, mission fails
23 }
24
25 // Absorb the asteroid's mass after destroying it
26 currentMass += asteroidMass;
27 }
28
29 // All asteroids successfully destroyed
30 return true;
31 }
32}
33
1class Solution {
2public:
3 bool asteroidsDestroyed(int mass, vector<int>& asteroids) {
4 // Sort asteroids in ascending order to destroy smaller ones first
5 ranges::sort(asteroids);
6
7 // Use long long to prevent integer overflow when accumulating mass
8 long long currentMass = mass;
9
10 // Iterate through each asteroid in sorted order
11 for (int asteroidMass : asteroids) {
12 // Check if current mass is sufficient to destroy this asteroid
13 if (currentMass < asteroidMass) {
14 return false; // Cannot destroy this asteroid, mission fails
15 }
16 // Absorb the asteroid's mass after destroying it
17 currentMass += asteroidMass;
18 }
19
20 // All asteroids successfully destroyed
21 return true;
22 }
23};
24
1/**
2 * Determines if a planet can destroy all asteroids by absorbing their mass
3 * @param mass - Initial mass of the planet
4 * @param asteroids - Array of asteroid masses
5 * @returns true if all asteroids can be destroyed, false otherwise
6 */
7function asteroidsDestroyed(mass: number, asteroids: number[]): boolean {
8 // Sort asteroids in ascending order to tackle smaller ones first
9 asteroids.sort((a: number, b: number) => a - b);
10
11 // Iterate through each asteroid
12 for (const asteroidMass of asteroids) {
13 // Check if current planet mass is sufficient to destroy the asteroid
14 if (mass < asteroidMass) {
15 return false;
16 }
17 // Absorb the asteroid's mass into the planet
18 mass += asteroidMass;
19 }
20
21 // All asteroids have been successfully destroyed
22 return true;
23}
24
Time and Space Complexity
Time Complexity: O(n ร log n)
The dominant operation in this code is the asteroids.sort()
call, which uses Python's Timsort algorithm with a time complexity of O(n ร log n)
where n
is the number of asteroids. After sorting, the code iterates through the sorted array once with a for loop, which takes O(n)
time. Since O(n ร log n) + O(n) = O(n ร log n)
, the overall time complexity is O(n ร log n)
.
Space Complexity: O(log n)
Python's built-in sort()
method sorts the list in-place and uses Timsort algorithm. While the sorting is done in-place (modifying the original list), Timsort requires O(log n)
auxiliary space for its merge operations and maintaining the stack of pending runs during the sorting process. The rest of the code uses only O(1)
additional space for the loop variable and comparisons. Therefore, the overall space complexity is O(log n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Integer Overflow Issues
One critical pitfall that's often overlooked is the potential for integer overflow when accumulating the planet's mass. As the planet absorbs asteroid masses, the total mass can grow exponentially and potentially exceed the maximum integer value in some programming languages.
The Problem:
# This could cause issues with very large numbers mass += asteroid_mass # What if this exceeds max integer?
Example scenario:
- Initial mass: 10^9
- Asteroids: [10^9, 10^9, 10^9, ...]
- After absorbing several asteroids, the mass could exceed typical integer limits
Solutions:
- Use appropriate data types:
class Solution:
def asteroidsDestroyed(self, mass: int, asteroids: List[int]) -> bool:
# Python handles arbitrary precision integers automatically
# But in other languages, you might need:
# - Use long long in C++
# - Use BigInteger in Java
# - Check for overflow conditions explicitly
asteroids.sort()
for asteroid_mass in asteroids:
if mass < asteroid_mass:
return False
# In Python, this is safe, but consider overflow in other languages
mass += asteroid_mass
return True
- Early termination optimization:
class Solution:
def asteroidsDestroyed(self, mass: int, asteroids: List[int]) -> bool:
asteroids.sort()
# Track the sum of all asteroids we've destroyed
total_absorbed = 0
for asteroid_mass in asteroids:
if mass + total_absorbed < asteroid_mass:
return False
total_absorbed += asteroid_mass
# Optional: Early termination if we've grown large enough
# to destroy any remaining asteroid
if mass + total_absorbed >= sum(asteroids):
return True
return True
2. Modifying Input Array In-Place
Another pitfall is sorting the input array in-place, which modifies the original data structure. This could be problematic if the caller expects the array to remain unchanged.
The Problem:
asteroids.sort() # This modifies the original array!
Solution - Create a copy:
class Solution:
def asteroidsDestroyed(self, mass: int, asteroids: List[int]) -> bool:
# Create a copy to avoid modifying the original array
sorted_asteroids = sorted(asteroids) # Creates a new sorted list
for asteroid_mass in sorted_asteroids:
if mass < asteroid_mass:
return False
mass += asteroid_mass
return True
3. Not Considering Edge Cases
Missing edge case checks:
class Solution:
def asteroidsDestroyed(self, mass: int, asteroids: List[int]) -> bool:
# Add edge case handling
if not asteroids: # Empty asteroid list
return True
if mass <= 0: # Invalid initial mass
return False if asteroids else True
asteroids.sort()
for asteroid_mass in asteroids:
if mass < asteroid_mass:
return False
mass += asteroid_mass
return True
These pitfalls highlight the importance of considering numerical limits, data integrity, and edge cases even in seemingly straightforward greedy algorithms.
What data structure does Breadth-first search typically uses to store intermediate states?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Donโt Miss This!