Facebook Pixel

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 planet
  • asteroids: an array of integers where asteroids[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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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?

  1. 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.

  2. 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.

Learn more about Greedy and Sorting patterns.

Solution Approach

The implementation follows a straightforward sorting + greedy strategy:

  1. 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.

  2. Iterate through the sorted asteroids: We process each asteroid one by one using a simple for loop: for x in asteroids:

  3. 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 return False since it's impossible to destroy all asteroids.
    • If mass >= x: The planet successfully destroys the asteroid.
  4. 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.

  5. Return the final result: If we successfully iterate through all asteroids without returning False, it means all asteroids have been destroyed, so we return True.

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 Evaluator

Example 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:

  1. 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
  1. 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What data structure does Breadth-first search typically uses to store intermediate states?


Recommended Readings

Want a Structured Path to Master System Design Too? Donโ€™t Miss This!

Load More