735. Asteroid Collision
Problem Description
You are given an array asteroids
of integers representing asteroids in a row. Each asteroid's position in the array represents its relative position in space.
For each asteroid:
- The absolute value represents its size
- The sign represents its direction (positive = moving right, negative = moving left)
- All asteroids move at the same speed
When two asteroids collide:
- The smaller asteroid explodes and disappears
- If both asteroids are the same size, both explode and disappear
- Asteroids moving in the same direction will never meet
Your task is to find the final state of all asteroids after all possible collisions have occurred.
For example, if you have asteroids [5, 10, -5]
:
- The asteroid
10
and-5
collide - Since
10
is larger than|-5|
, the-5
asteroid explodes - The final state is
[5, 10]
Another example with [8, -8]
:
- The asteroids
8
and-8
collide - Since they are the same size, both explode
- The final state is
[]
The solution uses a stack to simulate the collision process. As we traverse each asteroid from left to right, we check if it will collide with asteroids already in the stack. Right-moving asteroids (positive values) are added directly to the stack. Left-moving asteroids (negative values) may collide with right-moving asteroids in the stack, and we handle these collisions by comparing their sizes and removing destroyed asteroids accordingly.
Intuition
When thinking about this problem, we need to consider when collisions actually happen. Two asteroids collide only when a right-moving asteroid (positive) meets a left-moving asteroid (negative), and the right-moving one must come before the left-moving one in the array.
The key insight is that we're processing asteroids from left to right, simulating their movement through space. At any point in time, we need to keep track of which asteroids are still "alive" and haven't been destroyed by collisions.
A stack is perfect for this because:
- We process asteroids one by one from left to right
- A new asteroid can only collide with asteroids that came before it (and survived)
- When a left-moving asteroid appears, it will first collide with the most recent right-moving asteroid (the one at the top of the stack)
Think of it like this: the stack represents the current state of surviving asteroids. When we encounter a right-moving asteroid (x > 0
), it moves away from all previous asteroids, so no collision occurs - we simply add it to our stack.
The interesting case is when we encounter a left-moving asteroid (x < 0
). This asteroid moves toward the left, potentially colliding with any right-moving asteroids before it. The collision happens with the closest right-moving asteroid first (which is at the top of our stack). We keep checking collisions and removing destroyed asteroids until either:
- The left-moving asteroid is destroyed (it met a larger right-moving one)
- All right-moving asteroids in its path are destroyed
- It meets another left-moving asteroid (they move in the same direction, no collision)
- The stack becomes empty (no more asteroids to collide with)
This naturally leads to a stack-based simulation where we maintain the surviving asteroids and handle collisions as we process each new asteroid.
Learn more about Stack patterns.
Solution Approach
We use a stack to simulate the asteroid collisions as we traverse the array from left to right. Here's how the implementation works:
For each asteroid x
in the array:
Case 1: Right-moving asteroid (x > 0
)
- Simply push it onto the stack
- It moves away from all previous asteroids, so no collision occurs
stk.append(x)
Case 2: Left-moving asteroid (x < 0
)
This is where collisions may happen. We need to handle multiple scenarios:
First, we check for collisions with right-moving asteroids:
- While the stack is not empty AND the top element is positive (right-moving) AND the top element is smaller than
|x|
:- The right-moving asteroid explodes, so we pop it:
stk.pop()
- Continue checking with the next asteroid in the stack
- The right-moving asteroid explodes, so we pop it:
- This loop continues until we find a right-moving asteroid that's large enough to survive, or until there are no more right-moving asteroids
After the loop, we have three possible situations:
-
Equal-sized collision: If the top of stack equals
|x|
(i.e.,stk[-1] == -x
)- Both asteroids explode
- Pop the top element and don't add
x
to the stack
-
No collision or left-moving asteroid on top: If the stack is empty OR the top element is negative
- The left-moving asteroid
x
survives - Add it to the stack:
stk.append(x)
- The left-moving asteroid
-
Larger right-moving asteroid on top: If neither condition above is met
- The left-moving asteroid
x
is destroyed by the larger right-moving asteroid - We don't add
x
to the stack (it exploded)
- The left-moving asteroid
Finally, return the stack which contains all surviving asteroids in their final positions.
The algorithm efficiently handles all collisions in a single pass with time complexity O(n)
where each asteroid is pushed and popped at most once.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the example asteroids = [2, -1, 1, -2]
:
Initial state: Stack is empty []
Step 1: Process asteroid 2
(right-moving)
- Since it's positive, add it directly to the stack
- Stack:
[2]
Step 2: Process asteroid -1
(left-moving)
- Check for collision with top of stack (
2
) - Since
2 > |-1|
, asteroid-1
is destroyed - Stack remains:
[2]
Step 3: Process asteroid 1
(right-moving)
- Since it's positive, add it directly to the stack
- Stack:
[2, 1]
Step 4: Process asteroid -2
(left-moving)
- Check for collision with top of stack (
1
) - Since
1 < |-2|
, asteroid1
is destroyed, pop it - Stack becomes:
[2]
- Continue checking: top is now
2
- Since
2 = |-2|
, both asteroids destroy each other - Pop
2
from stack - Stack becomes:
[]
Final result: []
- all asteroids have been destroyed through collisions.
Let's trace through one more example to solidify understanding: asteroids = [10, 2, -5]
Step 1: Process 10
→ Stack: [10]
Step 2: Process 2
→ Stack: [10, 2]
Step 3: Process -5
- Check collision with
2
: Since2 < |-5|
, pop2
→ Stack:[10]
- Check collision with
10
: Since10 > |-5|
,-5
is destroyed - Final Stack:
[10]
Result: [10]
Solution Implementation
1class Solution:
2 def asteroidCollision(self, asteroids: List[int]) -> List[int]:
3 """
4 Simulate asteroid collisions where positive values move right and negative values move left.
5 When asteroids collide, the smaller one explodes; if equal size, both explode.
6
7 Args:
8 asteroids: List of integers representing asteroid sizes and directions
9
10 Returns:
11 List of asteroids remaining after all collisions
12 """
13 stack = []
14
15 for asteroid in asteroids:
16 # Positive asteroid moves right, add to stack
17 if asteroid > 0:
18 stack.append(asteroid)
19 else:
20 # Negative asteroid moves left, check for collisions
21 # Keep destroying smaller right-moving asteroids
22 while stack and stack[-1] > 0 and stack[-1] < -asteroid:
23 stack.pop()
24
25 # Equal size asteroids destroy each other
26 if stack and stack[-1] == -asteroid:
27 stack.pop()
28 # No collision occurs: either stack is empty or top asteroid also moves left
29 elif not stack or stack[-1] < 0:
30 stack.append(asteroid)
31 # Otherwise, the current left-moving asteroid is destroyed (implicit)
32
33 return stack
34
1class Solution {
2 public int[] asteroidCollision(int[] asteroids) {
3 // Use a stack to track asteroids that haven't been destroyed
4 Deque<Integer> stack = new ArrayDeque<>();
5
6 // Process each asteroid
7 for (int asteroid : asteroids) {
8 if (asteroid > 0) {
9 // Right-moving asteroid: always add to stack
10 stack.offerLast(asteroid);
11 } else {
12 // Left-moving asteroid: check for collisions with right-moving asteroids
13
14 // Destroy all smaller right-moving asteroids
15 while (!stack.isEmpty() && stack.peekLast() > 0 && stack.peekLast() < -asteroid) {
16 stack.pollLast();
17 }
18
19 // Check the outcome after destroying smaller asteroids
20 if (!stack.isEmpty() && stack.peekLast() == -asteroid) {
21 // Equal size collision: both asteroids are destroyed
22 stack.pollLast();
23 } else if (stack.isEmpty() || stack.peekLast() < 0) {
24 // No collision: either stack is empty or top asteroid is also left-moving
25 stack.offerLast(asteroid);
26 }
27 // If stack.peekLast() > -asteroid, current asteroid is destroyed (do nothing)
28 }
29 }
30
31 // Convert stack to array and return the result
32 return stack.stream().mapToInt(Integer::valueOf).toArray();
33 }
34}
35
1class Solution {
2public:
3 vector<int> asteroidCollision(vector<int>& asteroids) {
4 vector<int> stack;
5
6 for (int asteroid : asteroids) {
7 // If asteroid is moving right, always add it to stack
8 if (asteroid > 0) {
9 stack.push_back(asteroid);
10 }
11 // If asteroid is moving left, handle potential collisions
12 else {
13 // Keep destroying smaller right-moving asteroids
14 while (!stack.empty() && stack.back() > 0 && stack.back() < -asteroid) {
15 stack.pop_back();
16 }
17
18 // Check if current left-moving asteroid collides with equal-sized right-moving asteroid
19 if (!stack.empty() && stack.back() == -asteroid) {
20 stack.pop_back(); // Both asteroids destroy each other
21 }
22 // Add left-moving asteroid if no collision or only left-moving asteroids in stack
23 else if (stack.empty() || stack.back() < 0) {
24 stack.push_back(asteroid);
25 }
26 // If stack.back() > -asteroid, the right-moving asteroid survives (do nothing)
27 }
28 }
29
30 return stack;
31 }
32};
33
1/**
2 * Simulates asteroid collisions in a row.
3 * Positive values represent asteroids moving right, negative values represent asteroids moving left.
4 * When asteroids collide, the smaller one explodes. If both are equal size, both explode.
5 *
6 * @param asteroids - Array of integers representing asteroid sizes and directions
7 * @returns Array of asteroids that remain after all collisions
8 */
9function asteroidCollision(asteroids: number[]): number[] {
10 // Stack to track surviving asteroids
11 const stack: number[] = [];
12
13 for (const asteroid of asteroids) {
14 if (asteroid > 0) {
15 // Asteroid moving right - no collision possible with stack top
16 stack.push(asteroid);
17 } else {
18 // Asteroid moving left - check for collisions with right-moving asteroids
19
20 // Remove all smaller right-moving asteroids that collide with current asteroid
21 while (stack.length > 0 &&
22 stack[stack.length - 1] > 0 &&
23 stack[stack.length - 1] < Math.abs(asteroid)) {
24 stack.pop();
25 }
26
27 // Check if current asteroid collides with equal-sized right-moving asteroid
28 if (stack.length > 0 && stack[stack.length - 1] === Math.abs(asteroid)) {
29 // Both asteroids destroy each other
30 stack.pop();
31 } else if (stack.length === 0 || stack[stack.length - 1] < 0) {
32 // No collision: either stack is empty or top asteroid is also moving left
33 stack.push(asteroid);
34 }
35 // If stack top is a larger right-moving asteroid, current asteroid is destroyed (do nothing)
36 }
37 }
38
39 return stack;
40}
41
Time and Space Complexity
Time Complexity: O(n)
Each asteroid is processed at most twice - once when it's added to the stack and potentially once when it's removed during a collision. The while loop inside the for loop might seem to create O(n²)
complexity, but each element can only be popped from the stack once. Therefore, across all iterations of the outer loop, the total number of operations is bounded by 2n
, giving us O(n)
time complexity, where n
is the length of the asteroids
array.
Space Complexity: O(n)
The stack stk
is used to store asteroids that haven't been destroyed by collisions. In the worst case, when all asteroids are moving in the same direction (all positive or all negative), no collisions occur and all n
asteroids remain in the stack. Therefore, the space complexity is O(n)
, where n
is the length of the asteroids
array.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall: Incorrect Collision Logic Order
One of the most common mistakes is mishandling the order of collision checks after the while loop. Developers often write conditions that don't properly account for all scenarios, leading to incorrect results.
Incorrect Implementation:
# WRONG: Checking for survival before checking for mutual destruction while stack and stack[-1] > 0 and stack[-1] < -asteroid: stack.pop() # This order is problematic if not stack or stack[-1] < 0: stack.append(asteroid) elif stack and stack[-1] == -asteroid: # This may never be reached correctly stack.pop()
Why it's wrong: If we check for the asteroid's survival first, we might add a left-moving asteroid to the stack even when it should have been destroyed in a mutual collision with an equal-sized right-moving asteroid.
Correct Implementation:
while stack and stack[-1] > 0 and stack[-1] < -asteroid: stack.pop() # Check for mutual destruction FIRST if stack and stack[-1] == -asteroid: stack.pop() # Then check if the asteroid survives elif not stack or stack[-1] < 0: stack.append(asteroid) # Implicitly, if neither condition is met, the asteroid is destroyed
Pitfall: Off-by-One Error in Size Comparison
Another frequent error occurs when comparing asteroid sizes, forgetting that left-moving asteroids are negative.
Incorrect Implementation:
# WRONG: Comparing without considering the sign while stack and stack[-1] > 0 and stack[-1] < asteroid: # asteroid is negative! stack.pop()
Correct Implementation:
# RIGHT: Compare absolute values while stack and stack[-1] > 0 and stack[-1] < -asteroid: # -asteroid gives the absolute value stack.pop()
Pitfall: Not Handling Multiple Collisions
Some implementations fail to use a while loop for handling collisions, using an if statement instead, which only handles a single collision.
Incorrect Implementation:
# WRONG: Only handles one collision if stack and stack[-1] > 0 and stack[-1] < -asteroid: stack.pop() # What if there's another right-moving asteroid that should also collide?
Correct Implementation:
# RIGHT: Handles all consecutive collisions while stack and stack[-1] > 0 and stack[-1] < -asteroid: stack.pop()
Example demonstrating the issue:
- Input:
[1, 2, 3, -5]
- With
if
: Would only destroy3
, incorrectly returning[1, 2, -5]
- With
while
: Correctly destroys3
,2
, and1
, returning[-5]
Which of these pictures shows the visit order of a depth-first search?
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!