题目描述
给定一个整数数组 asteroids
,表示在同一行的行星。
对于数组中的每一个元素,其绝对值表示行星的大小,正负表示行星的移动方向(正表示向右移动,负表示向左移动)。每一颗行星以相同的速度移动。
找出碰撞后剩下的所有行星。碰撞规则:两个行星相互碰撞,较小的行星会爆炸。如果两颗行星大小相同,则两颗行星都会爆炸。两颗移动方向相同的行星,永远不会发生碰撞
数组 asteroids 的长度不超过 10000 。
每一颗行星的大小都是非零整数,范围是 [-1000, 1000] 。
PS: 题目没说,但是要按照从左往右的顺序返回。
样例
输入:
asteroids = [5, 10, -5]
输出: [5, 10]
解释:
10 和 -5 碰撞后只剩下 10。 5 和 10 永远不会发生碰撞。
输入:
asteroids = [8, -8]
输出: []
解释:
8 和 -8 碰撞后,两者都发生爆炸。
输入:
asteroids = [10, 2, -5]
输出: [10]
解释:
2 和 -5 发生碰撞后剩下 -5。10 和 -5 发生碰撞后剩下 10。
输入:
asteroids = [-2, -1, 1, 2]
输出: [-2, -1, 1, 2]
解释:
-2 和 -1 向左移动,而 1 和 2 向右移动。
由于移动方向相同的行星不会发生碰撞,所以最终没有行星发生碰撞。
算法
(栈模拟) O(n)
- 枚举每一个行星进行碰撞模拟 (借助一个栈来优化模拟速度)
步骤:
1. 从左到右枚举所有元素,看看当前元素只有在负数(向左),栈顶是正数(向右)的时候才会撞(-> <-
这样才会撞上),其余情况都是进栈等着被后续的行星撞。
2. 若能撞,当前元素直接把前面能撞的全部撞完,撞到不能撞或者自己被消灭为止。
3. 遍历完栈中的元素就是留下来(不相互冲突)的行星。
解释:
栈中的元素相当于在当前行星到来之前,之前的行星的剩余状况,只需要看当前行星和剩余状况的碰撞情况就可以判断该行星是否被摧毁,或者保留。
因为正数是向右撞的,负数是向左撞的。所以只有栈顶元素是正,当前元素是负的情况才会撞上。
栈永远保存的是不相互冲突的状态
C++ 代码
class Solution {
private:
vector<int> nums;
vector<int> ans;
stack<int> st;
void boom(int x) {
while (!st.empty() && st.top() > 0 && -x > st.top()) { // 只要能撞,并且向左撞的行星比向右的大,就把向栈顶的行星消灭.直到碰到能把自己消灭或者前面一个都不能打为止
st.pop();
}
if (!st.empty() && st.top() > 0 && st.top() >= -x) { // 会被前面行星消灭的情况
if (st.top() == -x) st.pop(); // 两个同归于尽了
// 其余情况被消灭了。即什么也不做。
} else st.push(x); // 其余情况行星暂时得以保留,前面一个能打的都没有,等待后面可能来的行星撞它
}
public:
vector<int> asteroidCollision(vector<int>& asteroids) {
ans.clear(); nums = asteroids;
if (nums.size() == 0) return ans;
for (const auto& x : nums) {
if (st.empty()) { // 若前面没有行星,直接先保留
st.push(x); continue;
}
if(x < 0 && st.top() > 0) boom(x); // 因为正数是向右撞的,负数是向左撞的。所以只有栈顶元素是正,当前元素是负的情况才会撞上。
else st.push(x); // 否则不会撞上
}
// 整理答案输出
while (!st.empty()) ans.push_back(st.top()), st.pop();
reverse(ans.begin(), ans.end());
return ans;
}
};