题目描述
给定一个整数数组 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 。
解题过程
思路及步骤
(1)创建 Stack 额外空间,方便判断某一元素是否会被碰撞掉;
(2)因为只有符号相反的两个数才有可能发生碰撞,所以我们针对负数来进行判断(建议使用负数);
(3)外层 for 循环遍历原数组;内层 while 循环去判断该元素是否需要入栈,或者被碰撞掉;
(4)还需要一个 flag 标记去识别当前元素是否需要进行入栈;
(5)以负数为例,满足 while 循环的条件是:栈不为空且当前元素为负数;
(6)如果当前元素是负数,且栈顶元素是负数,那么直接 break 掉 while 循环,flag 置为 true,表示当前元素需要入栈,同时也表明符号相同的两个数永远不会发生碰撞;
(7)如果当前元素是负数,并且栈顶元素的绝对值小于当前元素的绝对值,则栈顶元素出栈,表示栈顶元素被碰撞掉了,继续 while 循环,寻找 break 的条件;
(8)如果当前元素是负数,并且栈顶元素的绝对值大于当前元素的绝对值,此时当前元素就会被碰撞掉,那么直接 break 掉 while 循环,flag 置为 false;
(9)如果当前元素为负数,并且栈顶元素的绝对值等于当前元素的绝对值,此时栈顶元素和当前元素都会被碰撞掉,那么在 break 掉 while 循环的同时将 flag 置为 true;
(10)其他情况(栈为空或者当前元素为正数)的话,不满足 while 循环的条件,直接进行入栈操作即可;
(11)将栈中的元素赋值给最终的结果数组即可
注意点:
(1)符号相同的两个数永远不会发生碰撞;
(2)当前元素为负数且当前元素的上一个元素为正数时才会发生碰撞,当前元素为正数且当前元素的上一个元素为负数时不会发生碰撞,这正是我们使用栈来存储数据的优势,同时也是 while 循环为什么使用当前元素为负数来作为判断条件的原因所在
代码展示
public class AsteroidCollision {
public static int[] asteroidCollision(int[] asteroids) {
Stack<Integer> tempStack = new Stack<>();
for (int i = 0; i < asteroids.length; i++) {
// 是否入栈标识, true-入栈, false-不入栈
Boolean flag = true;
// 如果栈不为空且当前元素为负数, 则判断
while (!tempStack.isEmpty() && asteroids[i] < 0) {
// 如果当前元素为负数且栈顶元素也为负数, 则 break 掉 while 循环, 当前元素入栈, 进行下一个元素的比较
// 如果两个数都为负数, 永远也不会发生碰撞
if (tempStack.peek() < 0) {
break;
}
// 如果栈顶元素的绝对值小于当前元素的绝对值, 则栈顶元素出栈, 表示栈顶元素已经被碰撞掉
if (Math.abs(tempStack.peek()) < Math.abs(asteroids[i])) {
tempStack.pop();
} else if (Math.abs(tempStack.peek()) > Math.abs(asteroids[i])) {
// 如果栈顶元素的绝对值大于当前元素的绝对值, 则 break 掉 while 循环, 表示当前元素已经被碰撞掉, 进行下一个元素的比较
flag = false;
break;
} else {
// 如果栈顶元素的绝对值等于当前元素的绝对值, 则栈顶元素出栈, 同时 break 掉 while 循环, 表示栈顶元素和当前元素均被碰撞掉, 进行下一个元素的比较
tempStack.pop();
flag = false;
break;
}
}
// 入栈
if (flag) {
tempStack.push(asteroids[i]);
}
}
// 将栈中的元素倒序赋值, 返回
int[] resultArray = new int[tempStack.size()];
for (int i = 0; i < resultArray.length; i++) {
resultArray[resultArray.length - i - 1] = tempStack.pop();
}
return resultArray;
}
public static void main(String[] args) {
int[] asteroids = {-2, -1, 1, 2};
int[] result = asteroidCollision(asteroids);
for (int i = 0; i < result.length; i++) {
System.out.printf("%3d", result[i]);
}
System.out.println();
}
}
图解示例
以 int[] asteroids = {-2, -1, 1, 2, -3, 4, 1} 为例:
(1)初始化:

(2)当 i = 0 时,当前元素为 -2,栈为空,不满足 while 循环,所以直接将当前元素 -2 入栈;

(3)当 i = 1 时,当前元素为 -1,栈不为空,满足 while 循环条件,进入 while 循环,栈顶元素为 -2,当前元素与栈顶元素符号相同,永远不会发生碰撞,break 掉 while 循环,将当前元素 -1 入栈;

(4)当 i = 2 时,当前元素为 1,栈不为空,不满足 while 循环条件,所以直接将当前元素 1 入栈;

(5)当 i = 3 时,当前元素为 2,栈不为空,不满足 while 循环条件,所以直接将当前元素 2 入栈;

(6)当 i = 4 时,当前元素为 -3,栈不为空,满足 while 循环条件,进入 while 循环,栈顶元素为 2,当前元素的绝对值大于栈顶元素的绝对值,则将栈顶元素出栈,表示栈顶元素 2 已经被碰撞掉,继续循环,此时栈顶元素为 1,其绝对值仍然小于当前元素的绝对值,则继续出栈,表示栈顶元素 1 也已经被碰撞掉,继续循环,此时栈顶元素为 -1,与当前元素符号相同,永远不会发生碰撞, break 掉 while 循环,同时将当前元素 -3 入栈;

(7)当 i = 5 时,当前元素为 4,栈不为空,不满足 while 循环的条件,所以直接将当前元素 4 入栈;这种情况属于:当前元素向右移动,上一个元素向左移动,越来越远,永远不会发生碰撞;

(8)当 i = 6 时,当前元素为 1,栈不为空,不满足 while 循环的条件,所以直接将当前元素 1 入栈;

(9)所以最终碰撞剩下的元素即为 -2, -1, -3, 4, 1
