452. 用最少数量的箭引爆气球
[此处请插入:排序后的气球区间与贪心射箭重叠示意图]
✨核心逻辑
本题采用 贪心算法 的策略:
- 按右边界排序:将所有气球的区间按照 右边界(结束坐标) 升序排列。这样可以保证每次射出的箭,都能尽可能靠右,从而最大可能地覆盖后续重叠的气球。
- 初始化弓箭:第一支箭一定需要射出,将其射击位置定为第一个气球的右边界
index = points[0][1]。 - 贪心判断与更新:
- 因为数组按右边界升序,后面气球的右边界一定大于等于当前
index。 - 如果当前气球的 左边界
points[i][0]大于 当前弓箭的射击位置index,说明该气球超出了这支箭的射程,无法被引爆。此时必须新增一支弓箭,并把箭的射击位置更新为这个新气球的右边界。 - 如果左边界 小于等于 当前弓箭位置,说明在这个区间内不用新增弓箭,它被上一支箭覆盖了。
- 因为数组按右边界升序,后面气球的右边界一定大于等于当前
🔥代码实现(含详细变量注释)
class Solution {
public int findMinArrowShots(int[][] points) {
// 贪心!边界情况:如果没有气球,直接返回 0
if (points.length == 0) return 0;
// 1. 根据气球的右边界(结束坐标)进行升序排序
// 使用 Integer.compare 规避减法溢出问题,这是一种更严谨的写法
Arrays.sort(points, (a, b) -> Integer.compare(a[1], b[1]));
// length:记录气球的个数
int length = points.length;
// num:记录最少需要射出的弓箭数量。因为一定存在气球,所以初始化为 1
int num = 1;
// index:记录当前弓箭的引爆位置(上一个已处理气球的右边界),初始为第一个气球的右边界
int index = points[0][1];
// i:遍历指针,从第二个气球开始,依次往后判断
for (int i = 1; i < length; i++) {
// 判断当前气球的左边界是否大于弓箭的当前位置
// 如果是,说明前面的箭无法引爆这个气球,必须新射出一支箭
if (points[i][0] > index) {
num++; // 需要的弓箭数量 +1
index = points[i][1]; // 将新弓箭的引爆位置更新为当前气球的右边界
}
// 否则,当前气球与前面的重叠,会被同一支箭引爆,不做任何操作
}
return num;
}
}
- ⏱️复杂度分析
时间复杂度O(N log N),其中 N 是气球的个数。主要的时间开销在于使用 Arrays.sort 进行排序,复杂度为 O(N log N);随后的线性遍历仅需 O(N),综合时间复杂度为 O(N log N)。
空间复杂度:O(log N) 或 O(1)(取决于排序算法的实现)。Java 的 Arrays.sort 底层基于双轴快排,需要占用递归栈空间,大致为 O(log N)。如果不考虑排序的栈开销,仅额外使用了 length、num、index、i 等常数个变量,空间消耗为 O(1)。