title: Asteroid Collision
slug: asteroid-collision
difficulty: medium
leetcode_id: 735
leetcode_url: https://leetcode.com/problems/asteroid-collision/
categories:
  - arrays
  - stack
patterns:
  - slug: monotonic-stack
    is_optimal: true

function_signature: "def asteroid_collision(asteroids: list[int]) -> list[int]:"

test_cases:
  visible:
    - input: { asteroids: [5, 10, -5] }
      expected: [5, 10]
    - input: { asteroids: [8, -8] }
      expected: []
    - input: { asteroids: [10, 2, -5] }
      expected: [10]
  hidden:
    - input: { asteroids: [-2, -1, 1, 2] }
      expected: [-2, -1, 1, 2]
    - input: { asteroids: [1, -1, -2, -2] }
      expected: [-2, -2]
    - input: { asteroids: [1, -2, -2, -2] }
      expected: [-2, -2, -2]

description: |
  We are given an array `asteroids` of integers representing asteroids in a row. The indices of the asteroids 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.

constraints: |
  - `2 <= asteroids.length <= 10^4`
  - `-1000 <= asteroids[i] <= 1000`
  - `asteroids[i] != 0`

examples:
  - input: "asteroids = [5, 10, -5]"
    output: "[5, 10]"
    explanation: "The 10 and -5 collide resulting in 10. The 5 and 10 never collide."
  - input: "asteroids = [8, -8]"
    output: "[]"
    explanation: "The 8 and -8 collide exploding each other."
  - input: "asteroids = [10, 2, -5]"
    output: "[10]"
    explanation: "The 2 and -5 collide resulting in -5. The 10 and -5 collide resulting in 10."

explanation:
  intuition: |
    Imagine watching asteroids drift through space from above. Positive asteroids drift rightward (→), negative asteroids drift leftward (←). A collision only happens when a right-moving asteroid is followed by a left-moving one — they're heading toward each other.

    Think of it like cars on a one-lane road: two cars going the same direction never crash, and cars going opposite directions only crash if they're heading *toward* each other (right-moving in front, left-moving behind).

    The key insight is that we need to process asteroids in order and track the "survivors" — asteroids that haven't been destroyed yet. When we encounter a left-moving asteroid, we check if it will collide with any right-moving survivors behind it.

    This "check against previous survivors" pattern is a classic signal for using a **stack**: we push survivors onto the stack and pop them off when they get destroyed by incoming asteroids.

  approach: |
    We solve this using a **Stack Simulation**:

    **Step 1: Initialise an empty stack**

    - `stack`: Holds the asteroids that have survived so far
    - We'll process asteroids left-to-right and use the stack to track survivors

    &nbsp;

    **Step 2: Process each asteroid**

    For each asteroid, determine if it will collide with anything on the stack:

    - **No collision possible** if:
      - Stack is empty (nothing to collide with)
      - Top of stack is negative (both moving left, same direction)
      - Current asteroid is positive (moving right, away from stack)

    - **Collision happens** when:
      - Top of stack is positive (moving right)
      - Current asteroid is negative (moving left)
      - They're heading toward each other!

    &nbsp;

    **Step 3: Handle collisions**

    When a collision is detected:
    - Compare sizes: `abs(stack[-1])` vs `abs(asteroid)`
    - If stack asteroid is smaller → pop it, continue checking (the incoming asteroid might destroy more)
    - If stack asteroid is larger → incoming asteroid is destroyed, stop
    - If equal size → both explode (pop stack, don't push incoming)

    &nbsp;

    **Step 4: Push survivors**

    - If the incoming asteroid survives all collisions (or no collision occurred), push it onto the stack

    &nbsp;

    **Step 5: Return the stack**

    - The stack contains all surviving asteroids in their final positions

  common_pitfalls:
    - title: Forgetting That Collisions Can Chain
      description: |
        A single left-moving asteroid can destroy multiple right-moving asteroids in sequence.

        For example, with `[5, 10, -15]`:
        - `-15` first collides with `10` → `10` explodes
        - `-15` then collides with `5` → `5` explodes
        - Result: `[-15]`

        You must keep checking after each collision until the incoming asteroid is destroyed or no more collisions are possible.
      wrong_approach: "Only checking one collision per asteroid"
      correct_approach: "Loop until no collision or asteroid destroyed"

    - title: Misidentifying When Collisions Occur
      description: |
        Not all adjacent asteroids with opposite signs collide. The collision only happens when a **positive** (right-moving) asteroid is followed by a **negative** (left-moving) one.

        `[-5, 10]` → No collision! The `-5` is moving left, away from `10` which is moving right.

        `[10, -5]` → Collision! The `10` and `-5` are moving toward each other.
      wrong_approach: "Checking collision whenever signs differ"
      correct_approach: "Only collide when stack top is positive AND current is negative"

    - title: Equal Size Edge Case
      description: |
        When two asteroids of equal size collide, **both** are destroyed. This is easy to forget.

        For `[8, -8]`:
        - `8` goes on stack
        - `-8` collides with `8` (equal size)
        - Both explode → stack becomes empty
        - Result: `[]`
      wrong_approach: "Only destroying one asteroid on equal collision"
      correct_approach: "Pop stack AND don't push current when sizes are equal"

  key_takeaways:
    - "**Stack for collision simulation**: When elements can 'destroy' previous elements, a stack naturally tracks survivors"
    - "**Direction matters**: Collision only occurs when elements are moving *toward* each other, not just when they have opposite signs"
    - "**Chain reactions**: One incoming element can trigger multiple removals — always loop until stable"
    - "**Pattern recognition**: This is a variant of the 'monotonic stack' pattern where we maintain certain invariants about stack contents"

  time_complexity: "O(n). Each asteroid is pushed and popped from the stack at most once, giving us linear time."
  space_complexity: "O(n). In the worst case (all asteroids survive), the stack holds all `n` asteroids."

solutions:
  - approach_name: Stack Simulation
    is_optimal: true
    code: |
      def asteroid_collision(asteroids: list[int]) -> list[int]:
          stack = []

          for asteroid in asteroids:
              # Flag to track if current asteroid survives
              alive = True

              # Check for collisions: stack top moving right, current moving left
              while alive and stack and stack[-1] > 0 and asteroid < 0:
                  # Compare sizes (absolute values)
                  if stack[-1] < abs(asteroid):
                      # Stack asteroid is smaller, it explodes
                      stack.pop()
                      # Current asteroid continues (might hit more)
                  elif stack[-1] == abs(asteroid):
                      # Equal size: both explode
                      stack.pop()
                      alive = False
                  else:
                      # Stack asteroid is larger, current explodes
                      alive = False

              # If current asteroid survived, add it to stack
              if alive:
                  stack.append(asteroid)

          return stack
    explanation: |
      **Time Complexity:** O(n) — Each asteroid enters and leaves the stack at most once.

      **Space Complexity:** O(n) — Stack can hold all asteroids in worst case.

      We iterate through each asteroid and simulate collisions using a stack. The key insight is that a collision only occurs when the stack's top is positive (moving right) and the current asteroid is negative (moving left). We handle the three collision outcomes: smaller destroyed, larger survives, or equal means both destroyed.

  - approach_name: Simulation Without Stack
    is_optimal: false
    code: |
      def asteroid_collision(asteroids: list[int]) -> list[int]:
          result = []

          for asteroid in asteroids:
              # Process until no more collisions or asteroid destroyed
              while True:
                  # No collision if result empty, both same direction, or moving apart
                  if not result or result[-1] < 0 or asteroid > 0:
                      result.append(asteroid)
                      break

                  # Collision case: result[-1] > 0 and asteroid < 0
                  if result[-1] < abs(asteroid):
                      # Result asteroid smaller, remove it and continue
                      result.pop()
                  elif result[-1] == abs(asteroid):
                      # Equal: both destroyed
                      result.pop()
                      break
                  else:
                      # Result asteroid larger, current destroyed
                      break

          return result
    explanation: |
      **Time Complexity:** O(n) — Same as stack approach.

      **Space Complexity:** O(n) — Result list serves as our stack.

      This is functionally identical to the stack approach but uses the result list directly instead of a separate stack variable. The logic is the same: check for collisions when directions oppose, handle the three size comparison cases, and continue until stable.
