Simulate asteroid collisions using a stack that keeps the surviving asteroids in order.
Problem Restatement
We are given an integer array asteroids.
Each integer represents one asteroid.
The absolute value is the asteroid size.
The sign is its direction:
| Value | Direction |
|---|---|
| Positive | Moving right |
| Negative | Moving left |
All asteroids move at the same speed.
When two asteroids collide:
| Case | Result |
|---|---|
| Smaller asteroid | Explodes |
| Same size | Both explode |
| Same direction | They never collide |
We need to return the final state after all collisions. A collision only happens when a right-moving asteroid is before a left-moving asteroid. The official problem defines positive values as moving right, negative values as moving left, and asks for the state after all collisions.
Input and Output
| Item | Meaning |
|---|---|
| Input | A list of integers asteroids |
| Output | A list of surviving asteroids |
| Size | abs(asteroids[i]) |
| Direction | Sign of asteroids[i] |
| Important case | Only positive before negative can collide |
Example function shape:
def asteroidCollision(asteroids: list[int]) -> list[int]:
...Examples
Example 1
asteroids = [5, 10, -5]The asteroid 10 moves right.
The asteroid -5 moves left.
They collide.
Since 10 is larger than 5, -5 explodes.
The result is:
[5, 10]Example 2
asteroids = [8, -8]The two asteroids collide.
They have the same size, so both explode.
The result is:
[]Example 3
asteroids = [10, 2, -5]First, 2 and -5 collide.
-5 has size 5, so it destroys 2.
Now 10 and -5 collide.
10 is larger, so -5 explodes.
The result is:
[10]Example 4
asteroids = [-2, -1, 1, 2]The negative asteroids move left.
The positive asteroids move right.
They are moving away from each other, so no collision happens.
The result is:
[-2, -1, 1, 2]First Thought: Simulate Collisions
We can process asteroids from left to right.
When we see an asteroid, it may collide only with earlier asteroids that are still alive.
More specifically, a collision can happen only when:
previous asteroid > 0
current asteroid < 0That means the previous asteroid is moving right and the current asteroid is moving left.
The closest previous asteroid matters first. If it explodes, the current asteroid may continue and collide with the next previous asteroid.
This last-survivor-first-collision behavior suggests a stack.
Key Insight
Use a stack to store asteroids that have survived so far.
For each new asteroid x:
- If
xis moving right, push it. - If
xis moving left, it may collide with right-moving asteroids at the top of the stack. - While the top of the stack is positive and
xis negative, resolve the collision.
There are three collision outcomes:
| Condition | Result |
|---|---|
abs(x) > stack[-1] | Top asteroid explodes, continue checking |
abs(x) == stack[-1] | Both explode |
abs(x) < stack[-1] | Current asteroid explodes |
If the current asteroid survives all possible collisions, push it onto the stack.
Algorithm
Create an empty stack.
For each asteroid x in asteroids:
- Assume
xis alive. - While
xis alive, the stack is not empty,stack[-1] > 0, andx < 0:- If
stack[-1] < -x, pop the stack. - Else if
stack[-1] == -x, pop the stack and markxas destroyed. - Else mark
xas destroyed.
- If
- If
xis still alive, append it to the stack. - Return the stack.
Correctness
The stack stores exactly the surviving asteroids among the prefix already processed, in their original order.
When the next asteroid moves right, it cannot collide with any previous asteroid immediately. Any previous right-moving asteroid moves in the same direction, and any previous left-moving asteroid is moving away from it. So pushing it is correct.
When the next asteroid moves left, it can collide only with previous right-moving asteroids. Among those, the nearest surviving one is at the top of the stack. Therefore resolving collision against stack[-1] first is correct.
If the top asteroid is smaller, it explodes, and the current asteroid may continue left into the next surviving asteroid. Popping and continuing is correct.
If both have equal size, both explode. Popping the top and discarding the current asteroid is correct.
If the top asteroid is larger, the current asteroid explodes. Keeping the stack unchanged is correct.
After all possible collisions for the current asteroid are resolved, if it remains alive, no previous surviving asteroid can collide with it. Therefore appending it preserves the stack invariant.
After every asteroid is processed, the stack contains exactly the final surviving asteroids.
Complexity
Let n = len(asteroids).
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | Each asteroid is pushed once and popped at most once |
| Space | O(n) | The stack may store all asteroids |
Even though there is a nested while loop, the total number of pops across the whole algorithm is at most n.
Implementation
class Solution:
def asteroidCollision(self, asteroids: list[int]) -> list[int]:
stack = []
for x in asteroids:
alive = True
while alive and stack and stack[-1] > 0 and x < 0:
top = stack[-1]
if top < -x:
stack.pop()
elif top == -x:
stack.pop()
alive = False
else:
alive = False
if alive:
stack.append(x)
return stackCode Explanation
We use stack to store the asteroids that are still alive:
stack = []For each asteroid, we track whether it survives:
alive = TrueThe collision loop runs only in the one possible collision direction:
while alive and stack and stack[-1] > 0 and x < 0:If the stack top is smaller:
if top < -x:
stack.pop()the top asteroid explodes, and the current asteroid keeps moving left.
If both sizes are equal:
elif top == -x:
stack.pop()
alive = Falseboth explode.
If the stack top is larger:
else:
alive = Falsethe current asteroid explodes.
After the loop, a surviving asteroid is stored:
if alive:
stack.append(x)Testing
def run_tests():
s = Solution()
assert s.asteroidCollision([5, 10, -5]) == [5, 10]
assert s.asteroidCollision([8, -8]) == []
assert s.asteroidCollision([10, 2, -5]) == [10]
assert s.asteroidCollision([-2, -1, 1, 2]) == [-2, -1, 1, 2]
assert s.asteroidCollision([1, -2]) == [-2]
assert s.asteroidCollision([2, -1]) == [2]
assert s.asteroidCollision([1, 2, 3, -6]) == [-6]
assert s.asteroidCollision([-2, 2, -2, -1]) == [-2, -1]
print("all tests passed")
run_tests()| Test | Why |
|---|---|
[5, 10, -5] | Current left-moving asteroid is destroyed |
[8, -8] | Equal sizes destroy both |
[10, 2, -5] | One asteroid causes multiple collisions |
[-2, -1, 1, 2] | No collision because asteroids move away |
[1, -2] | Current asteroid destroys stack top |
[2, -1] | Stack top destroys current asteroid |
[1, 2, 3, -6] | Current asteroid destroys many asteroids |
[-2, 2, -2, -1] | Existing left movers remain safe |