小行星碰撞

标签: 数组 模拟

难度: Medium

给定一个整数数组 asteroids,表示在同一行的小行星。

对于数组中的每一个元素,其绝对值表示小行星的大小,正负表示小行星的移动方向(正表示向右移动,负表示向左移动)。每一颗小行星以相同的速度移动。

找出碰撞后剩下的所有小行星。碰撞规则:两个小行星相互碰撞,较小的小行星会爆炸。如果两颗小行星大小相同,则两颗小行星都会爆炸。两颗移动方向相同的小行星,永远不会发生碰撞。

示例 1:

输入:asteroids = [5,10,-5]
输出:[5,10]
解释:10 和 -5 碰撞后只剩下 10 。 5 和 10 永远不会发生碰撞。

示例 2:

输入:asteroids = [8,-8]
输出:[]
解释:8 和 -8 碰撞后,两者都发生爆炸。

示例 3:

输入:asteroids = [10,2,-5]
输出:[10]
解释:2 和 -5 发生碰撞后剩下 -5 。10 和 -5 发生碰撞后剩下 10 。

提示:

  • 2 <= asteroids.length <= 104
  • -1000 <= asteroids[i] <= 1000
  • asteroids[i] != 0

Submission

运行时间: 24 ms

内存: 17.0 MB

class Solution:
    def asteroidCollision(self, asteroids: List[int]) -> List[int]:
        a_stack = []
        for ele in asteroids:
            if not a_stack:
                a_stack.append(ele)
            else:
                if ele > 0:
                    a_stack.append(ele)
                else:
                    while a_stack:
                        if a_stack[-1] > -ele:
                            break
                        elif a_stack[-1] < 0:
                            a_stack.append(ele)
                            break
                        elif a_stack[-1] == -ele:
                            a_stack.pop()
                            break
                        else:
                            a_stack.pop()
                            if not a_stack:
                                a_stack.append(ele)
                                break
                    
        return a_stack

Explain

这个题解使用了栈的数据结构。遍历给定的小行星数组,对于每个小行星,根据其运动方向和栈顶小行星的情况进行处理。如果当前小行星向右运动(正数),直接将其压入栈中;如果向左运动(负数),则与栈顶的小行星进行比较。如果栈顶小行星向右运动且体积更大,则弹出栈顶小行星,继续比较,直到栈为空或者当前小行星体积更大。如果最终栈为空或者当前小行星存活,则将当前小行星压入栈中。遍历完整个数组后,栈中剩余的小行星即为碰撞后存活的小行星。

时间复杂度: O(n^2),其中 n 是数组的长度,但平均情况下接近 O(n)。

空间复杂度: O(n),其中 n 是数组的长度。

class Solution:
    def asteroidCollision(self, asteroids: List[int]) -> List[int]:
        a_stack = []
        for ele in asteroids:
            if not a_stack:
                # 如果栈为空,直接将当前小行星压入栈中
                a_stack.append(ele)
            else:
                if ele > 0:
                    # 如果当前小行星向右运动,直接压入栈中
                    a_stack.append(ele)
                else:
                    while a_stack:
                        if a_stack[-1] > -ele:
                            # 如果栈顶小行星向右运动且体积更大,当前小行星爆炸
                            break
                        elif a_stack[-1] < 0:
                            # 如果栈顶小行星向左运动,当前小行星压入栈中
                            a_stack.append(ele)
                            break
                        elif a_stack[-1] == -ele:
                            # 如果栈顶小行星与当前小行星体积相同,都爆炸
                            a_stack.pop()
                            break
                        else:
                            # 如果栈顶小行星向右运动且体积更小,栈顶小行星爆炸
                            a_stack.pop()
                            if not a_stack:
                                # 如果栈为空,当前小行星压入栈中
                                a_stack.append(ele)
                                break
                    
        return a_stack

Explore

栈是一种后进先出(LIFO)的数据结构,适合处理需要回溯元素以进行比较和决策的问题。在小行星碰撞的场景中,当新的小行星进入时,我们需要与之前的小行星比较并决定是否爆炸。这种比较和决策过程需要从最近的小行星开始,依次向前,直到找到决策点。栈允许我们方便地访问最后进入的小行星,并根据碰撞规则逐个处理,这符合问题的自然处理顺序。

如果小行星数组中的所有元素都是负数,这意味着所有小行星都向左运动。在这种情况下,由于没有向右运动的小行星与之相撞,所有这些向左运动的小行星将直接被添加到栈中,不会发生任何碰撞。结果,栈中最终将包含数组中的所有小行星,按照它们在数组中的顺序。

在题解中,如果当前向左运动的小行星体积大于栈顶向右运动的小行星体积,栈顶小行星会爆炸,然后继续比较下一个栈顶小行星。这个过程会持续,直到找到一个更大的向右运动的小行星或栈为空。如果栈为空或没有足够大的向右运动小行星来停止当前向左运动的小行星,那么该向左运动的小行星将被添加到栈中。因此,当前向左运动的小行星只有在面对更大的对立小行星时才会'爆炸',否则它会存活并最终被添加到栈中。

当栈顶小行星和当前小行星体积相等且方向相反时,根据物理碰撞的模拟,两者都将完全相互抵消。这种情况下,选择让两者都爆炸是为了模拟现实中的完全能量抵消现象。保留任何一方都将违反题目的碰撞逻辑,即两个体积相等且方向相反的物体在碰撞后都应该消失。这样的处理简化了逻辑并保持了问题描述的一致性。