← 返回题目列表

行星碰撞问题为什么适合用栈模拟?

高频 中等 第 17 / 30 题 更新于 2026/07/29
模拟碰撞

简化版

行星碰撞用栈保存已经稳定存在的行星。只有栈顶向右移动且当前行星向左移动时才会碰撞;比较绝对值,小的爆炸,相等都爆炸,当前行星如果没爆炸才入栈。

详细版

正数表示向右,负数表示向左。新行星从左到右扫描时,只可能和栈顶的右行星发生碰撞,因为更左侧的行星被栈顶挡住。循环处理连续碰撞,直到当前行星爆炸、栈空,或栈顶不再可能碰撞。

int[] asteroidCollision(int[] asteroids) {
    Deque<Integer> stack = new ArrayDeque<>();
    for (int a : asteroids) {
        boolean alive = true;
        while (alive && a < 0 && !stack.isEmpty() && stack.peek() > 0) {
            int top = stack.peek();
            if (top < -a) stack.pop();
            else if (top == -a) { stack.pop(); alive = false; }
            else alive = false;
        }
        if (alive) stack.push(a);
    }
    int[] ans = new int[stack.size()];
    for (int i = ans.length - 1; i >= 0; i--) ans[i] = stack.pop();
    return ans;
}

关键条件是 stack.peek() > 0 && a < 0,同向或背向而行都不会碰撞。

完整版教学

一、什么时候会发生碰撞

行星按数组顺序从左到右排列,正数向右,负数向左。只有左边的行星向右、右边的新行星向左时,它们才会迎面相撞。也就是栈顶 > 0 且当前 a < 0

左  ->      <-  右    会碰撞
左  <-      ->  右    越走越远,不碰撞
左  ->      ->  右    同向,不碰撞
左  <-      <-  右    同向,不碰撞

这个条件如果写错,会让很多本不该碰撞的情况被误删。

二、栈保存的是什么

栈保存已经扫描过、在当前视角下还存活的行星。新行星只需要和栈顶比较,因为栈顶是距离它最近的左侧存活行星;如果栈顶被撞碎,当前行星才有机会继续和更左边的下一个栈顶碰撞。

例如 [10,2,-5]-5 先撞 22 爆炸;然后 -5 继续撞 10,自己爆炸。这个连续过程正好是 while 弹栈。

三、三种碰撞结果

碰撞双方大小按绝对值比较:

情况结果当前行星是否继续
top < -a栈顶爆炸当前继续向左撞
top == -a双方爆炸当前结束
top > -a当前爆炸当前结束

这里 a 是负数,所以当前行星大小是 -a。写成 Math.abs(a) 也可以,但要注意不要混淆方向和大小。

四、手算示例

[5,10,-5] 为例,5 和 10 入栈后,-5 只会撞 10。因为 10 更大,-5 爆炸,结果 [5,10]

再看 [8,-8],-8 撞 8,大小相等,两个都爆炸,结果 []

[10, 2, -5]
栈 [10,2]
-5 撞 2 -> 2 爆
-5 撞 10 -> -5 爆
结果 [10]

这类题最重要的是把连续碰撞模拟完整。

五、结果顺序怎么恢复

如果用 push 把元素放到栈顶,最终栈顶是数组右侧的行星。输出时要从结果数组末尾向前填,才能恢复从左到右的顺序。

int[] ans = new int[stack.size()];
for (int i = ans.length - 1; i >= 0; i--) {
    ans[i] = stack.pop();
}

也可以用 offerLast / pollLast 把双端队列当作尾部栈使用,最后按队列顺序遍历,关键是保持方向一致。

六、常见误区与追问

记忆钩子:只看“栈顶向右、当前向左”这一种迎面相撞。撞碎栈顶就继续撞,当前碎了就停。

  • 误区:一正一负就一定碰撞。 [-2,1] 是背向而行,不会碰撞。
  • 误区:当前行星只碰撞一次。 它可能撞碎多个较小的右行星,需要 while。
  • 误区:相等时只弹栈顶。 大小相等双方都爆炸,当前不能入栈。
  • 追问:为什么只和栈顶比较? 栈顶是最近的左侧存活行星,更左边的行星被它挡住。
  • 追问:复杂度为什么 O(n)? 每个行星最多入栈一次、出栈一次,总操作线性。
  • 追问:如果有速度差怎么办? 本题默认速度相同;若速度不同,就变成事件模拟,不能直接套这个栈模型。

七、加强记忆

行星碰撞不是全局两两比较,而是从左到右维护“已稳定区域”。当前负行星只可能冲进左侧正行星堆,所以栈顶正数和当前负数是唯一碰撞入口。比较大小决定谁爆,当前活着就继续撞,最后活着才入栈。