Asteroid Collision
MedPrompt
We are given an array asteroids of integers representing asteroids in a row. The indices of the asteriod in the array represent their relative position in space.
For each asteroid, the absolute value represents its size, and the sign represents its direction (positive meaning right, negative meaning left). Each asteroid moves at the same speed.
Find out the state of the asteroids after all collisions. If two asteroids meet, the smaller one will explode. If both are the same size, both will explode. Two asteroids moving in the same direction will never meet.
Example 1:
Input: asteroids = [5,10,-5]
Output: [5,10]
Explanation: The 10 and -5 collide resulting in 10. The 5 and 10 never collide.Example 2:
Input: asteroids = [8,-8]
Output: []
Explanation: The 8 and -8 collide exploding each other.Example 3:
Input: asteroids = [10,2,-5]
Output: [10]
Explanation: The 2 and -5 collide resulting in -5. The 10 and -5 collide resulting in 10.
Constraints:
2 <= asteroids.length <= 104-1000 <= asteroids[i] <= 1000asteroids[i] != 0
Approaches
2 approaches with complexity analysis and trade-offs.
This approach directly simulates the collision process in a brute-force manner. It repeatedly scans the list of asteroids, finds the first pair that will collide, resolves the collision, and then restarts the scan. This continues until a full pass over the asteroids results in no collisions, at which point the asteroids are in a stable state.
Algorithm
-
- Convert the input array
asteroidsto aListto allow for dynamic resizing and element removal.
- Convert the input array
-
- Use a
whileloop that continues to run as long as collisions were found and resolved in the previous pass. A boolean flag can track this.
- Use a
-
- Inside the
whileloop, reset the flag and start aforloop to iterate through the current list of asteroids.
- Inside the
-
- In the
forloop, look for the first adjacent pair of asteroids(list[i], list[i+1])that will collide. A collision occurs iflist[i]is positive (moving right) andlist[i+1]is negative (moving left).
- In the
-
- If a colliding pair is found:
- a. Compare their absolute values to determine the outcome.
- b. If
|list[i]| > |list[i+1]|, removelist[i+1]. - c. If
|list[i]| < |list[i+1]|, removelist[i]. - d. If
|list[i]| == |list[i+1]|, remove both asteroids. - e. Set the boolean flag to
trueto indicate a collision happened, andbreakthe innerforloop to restart the scan from the beginning of the modified list.
-
- If the
forloop completes without finding any collisions, the flag remainsfalse, and the outerwhileloop terminates.
- If the
-
- Convert the final stable list back to an array and return it.
Walkthrough
In this method, we use a dynamic data structure like an ArrayList in Java to store the asteroids, as it allows for easy removal of elements. We then enter a loop that continues as long as we are finding and resolving collisions. In each iteration of this main loop, we scan through the list to find the first instance of a right-moving asteroid followed immediately by a left-moving one.
When we find such a pair, we resolve the collision based on their sizes. The smaller asteroid (in absolute value) is removed. If they are of equal size, both are removed. After a collision is resolved, we must restart the scan from the beginning of the now-modified list, because the result of the collision could potentially cause a new collision with a preceding asteroid. The process terminates when we can complete a full scan of the list without any collisions occurring.
import java.util.ArrayList;import java.util.List; class Solution { public int[] asteroidCollision(int[] asteroids) { List<Integer> list = new ArrayList<>(); for (int ast : asteroids) { list.add(ast); } boolean collisionOccurred = true; while (collisionOccurred) { collisionOccurred = false; for (int i = 0; i < list.size() - 1; i++) { // Check for a right-moving asteroid followed by a left-moving one if (list.get(i) > 0 && list.get(i+1) < 0) { collisionOccurred = true; // A collision is found if (Math.abs(list.get(i)) > Math.abs(list.get(i+1))) { list.remove(i + 1); } else if (Math.abs(list.get(i)) < Math.abs(list.get(i+1))) { list.remove(i); } else { list.remove(i + 1); list.remove(i); } // After a collision, restart the scan from the beginning break; } } } // Convert the final list to an array int[] result = new int[list.size()]; for (int i = 0; i < list.size(); i++) { result[i] = list.get(i); } return result; }}Complexity
Time
O(N^2), where N is the number of asteroids. In the worst-case scenario, we might only resolve one collision per pass (e.g., a large left-moving asteroid at the end colliding with multiple smaller right-moving asteroids one by one). Since removing an element from an `ArrayList` can take O(N) time, and we might do this O(N) times, the complexity is quadratic.
Space
O(N), where N is the number of asteroids. This is because we create a new `List` to hold the asteroids, which can store up to N elements.
Trade-offs
Pros
The logic is straightforward and directly models the problem description.
It's relatively easy to implement and understand for beginners.
Cons
Highly inefficient with a quadratic time complexity, O(N^2), which will be too slow for large inputs.
Repeatedly scanning the list from the beginning after each collision is computationally expensive.
Modifying a list while iterating over it adds complexity and potential for bugs if not handled carefully.
A much more efficient approach utilizes a Stack data structure. The logic is that collisions only happen when a right-moving asteroid is followed by a left-moving one. This 'last-in, first-out' interaction is perfectly modeled by a stack. We iterate through the asteroids, using the stack to keep track of the stable asteroids. An incoming asteroid only interacts with the one at the top of the stack.
Algorithm
-
- Initialize an empty
Stackto store the asteroids that have survived so far.
- Initialize an empty
-
- Iterate through each
asteroidfrom the input array one by one.
- Iterate through each
-
- For each
asteroid:
- a. If the
asteroidis positive (moving right), it won't collide with anything currently on the stack (as they are either also moving right or moving away). Push it onto the stack. - b. If the
asteroidis negative (moving left), it may collide with positive asteroids at the top of the stack. Use awhileloop to handle these collisions:- The loop continues as long as the stack is not empty, the top element is positive, and the incoming
asteroidis larger in magnitude (stack.peek() < -asteroid). In this case, the asteroid on the stack is destroyed, sopopit.
- The loop continues as long as the stack is not empty, the top element is positive, and the incoming
- c. After the
whileloop, check the state:- If the stack is now empty or the top element is also negative, the incoming
asteroidsurvived without being destroyed. Push it onto the stack. - If the top element of the stack is positive and has the same size (
stack.peek() == -asteroid), both are destroyed.popthe element from the stack and do not push the currentasteroid. - If the top element is positive and larger (
stack.peek() > -asteroid), the incomingasteroidis destroyed. Do nothing.
- If the stack is now empty or the top element is also negative, the incoming
- For each
-
- Once all asteroids have been processed, the stack contains the final state. Create a new array of the stack's size and populate it with the elements from the stack. Note that elements will be popped in reverse order.
Walkthrough
This optimal solution processes the asteroids in a single pass, achieving linear time complexity. We use a stack to maintain the collection of asteroids that are currently moving without collision.
We iterate through the input asteroids array. When we encounter a right-moving (positive) asteroid, it can't cause a collision with what's already in the stack, so we simply push it.
When we encounter a left-moving (negative) asteroid, a potential collision arises. We check the top of the stack. If the stack is empty or the top asteroid is also moving left, there's no collision, and we push the new asteroid. However, if the top is a right-moving (positive) asteroid, we handle the collision:
- If the right-moving asteroid on the stack is smaller than the incoming left-moving one (in absolute value), the one on the stack is destroyed. We pop it and repeat the check with the new top of the stack.
- If they are of equal size, both are destroyed. We pop the stack and discard the incoming asteroid.
- If the right-moving asteroid on the stack is larger, the incoming left-moving one is destroyed. We do nothing further with it.
After iterating through all asteroids, the stack holds the final configuration. We then convert the stack's contents into an array to return.
import java.util.Stack; class Solution { public int[] asteroidCollision(int[] asteroids) { Stack<Integer> stack = new Stack<>(); for (int ast : asteroids) { if (ast > 0) { // Right-moving asteroid, no immediate collision with stack contents stack.push(ast); } else { // ast < 0, left-moving asteroid // Destroy smaller right-moving asteroids on the stack while (!stack.isEmpty() && stack.peek() > 0 && stack.peek() < -ast) { stack.pop(); } if (stack.isEmpty() || stack.peek() < 0) { // If stack is empty or top is also left-moving, no collision stack.push(ast); } else if (stack.peek() == -ast) { // If same size, both are destroyed stack.pop(); } // else stack.peek() > -ast, so incoming asteroid is destroyed. Do nothing. } } // Convert stack to array int[] result = new int[stack.size()]; for (int i = result.length - 1; i >= 0; i--) { result[i] = stack.pop(); } return result; }}Complexity
Time
O(N), where N is the number of asteroids. Each asteroid is pushed onto the stack at most once. While the inner `while` loop may run multiple times for a single incoming asteroid, each `pop` operation removes an element permanently. Thus, the total number of push and pop operations across the entire execution is proportional to N.
Space
O(N), where N is the number of asteroids. In the worst-case scenario, if all asteroids are moving in the same direction (e.g., all positive or all negative), they will all be pushed onto the stack.
Trade-offs
Pros
Optimal time complexity of O(N) as it processes each asteroid in a single pass.
The use of a stack provides an elegant and intuitive solution to the problem's collision dynamics.
Handles all collision cases efficiently and correctly.
Cons
Requires extra space for the stack, which can be O(N) in the worst case (e.g., all asteroids moving in the same direction).
Solutions
Solution
class Solution {public int[] asteroidCollision(int[] asteroids) { Deque<Integer> stk = new ArrayDeque<>(); for (int x : asteroids) { if (x > 0) { stk.offerLast(x); } else { while (!stk.isEmpty() && stk.peekLast() > 0 && stk.peekLast() < -x) { stk.pollLast(); } if (!stk.isEmpty() && stk.peekLast() == -x) { stk.pollLast(); } else if (stk.isEmpty() || stk.peekLast() < 0) { stk.offerLast(x); } } } return stk.stream().mapToInt(Integer : : valueOf).toArray(); }}class Solution: def asteroidCollision(self, asteroids: List[int]) -> List[int]: stk = [] for x in asteroids: if x > 0: stk . append(x) else: while stk and stk[- 1] > 0 and stk[- 1] < - x: stk . pop() if stk and stk[- 1] == - x: stk . pop() elif not stk or stk[- 1] < 0: stk . append(x) return stkclass Solution {public: vector<int> asteroidCollision(vector<int> &asteroids) { vector<int> stk; for (int x : asteroids) { if (x > 0) { stk.push_back(x); } else { while (stk.size() && stk.back() > 0 && stk.back() < -x) { stk.pop_back(); } if (stk.size() && stk.back() == -x) { stk.pop_back(); } else if (stk.empty() || stk.back() < 0) { stk.push_back(x); } } } return stk; }};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.