问题描述

有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。

一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstartxend, 且满足  xstart ≤ x ≤ xend,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。

给你一个数组 points ,返回引爆所有气球所必须射出的 最小 弓箭数 。

示例 1:

输入:points = [[10,16],[2,8],[1,6],[7,12]]
输出:2
解释:气球可以用2支箭来爆破:

  • 在x = 6处射出箭,击破气球[2,8]和[1,6]。
  • 在x = 11处发射箭,击破气球[10,16]和[7,12]。

示例 2:

输入:points = [[1,2],[3,4],[5,6],[7,8]]
输出:4
解释:每个气球需要射出一支箭,总共需要4支箭。

示例 3:

输入:points = [[1,2],[2,3],[3,4],[4,5]]
输出:2
解释:气球可以用2支箭来爆破:

  • 在x = 2处发射箭,击破气球[1,2]和[2,3]。
  • 在x = 4处射出箭,击破气球[3,4]和[4,5]。

提示:

  • 1 <= points.length <= 10^5
  • points[i].length == 2
  • -2^31 <= xstart < xend <= 2^31 - 1

核心思路

核心思路

考虑所有气球中右边界位置最靠左的那一个,那么一定有一支箭的射出位置就是它的右边界(否则就没有箭可以将其引爆了)。当我们确定了一支箭之后,我们就可以将这支箭引爆的所有气球移除,并从剩下未被引爆的气球中,再选择右边界位置最靠左的那一个,确定下一支箭,直到所有的气球都被引爆。

实现要点

在内层的 jj 循环中,当我们遇到第一个不满足 x(j)y(i)x(j) \leq y(i)jj 值,就可以直接跳出循环,并且这个 y(j)y(j) 就是下一支箭的射出位置。为什么这样做是对的呢?我们考虑某一支箭的索引 iti_t​ 以及它的下一支箭的索引 jtj_t ,对于索引在 jtj_t 之后的任意一个可以被 iti_t 引爆的气球,记索引为 j0j_0 ,有:

x(j0)y(it)x(j_0) \leq y(i_t)

由于 y(it)y(jt)y(i_t) \leq y(j_t) 显然成立,那么

x(j0)y(jt)x(j_0) \leq y(j_t)

也成立,也就是说:当前这支箭在索引 jtj_t (第一个无法引爆的气球)之后所有可以引爆的气球,下一支箭也都可以引爆。

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
int findMinArrowShots(vector<vector<int>>& points) {
if (points.empty()) {
return 0;
}
sort(points.begin(), points.end(), [](const vector<int>& u, const vector<int>& v) {
return u[1] < v[1];
});
int pos = points[0][1];
int ans = 1;
for (const vector<int>& balloon: points) {
if (balloon[0] > pos) {
pos = balloon[1];
++ans;
}
}
return ans;
}
};
  • 时间复杂度:O(nlogn)O(n\log n),其中 nn 是数组 points\textit{points} 的长度。排序的时间复杂度为 O(nlogn)O(n \log n),对所有气球进行遍历并计算答案的时间复杂度为 O(n)O(n),其在渐进意义下小于前者,因此可以忽略。

  • 空间复杂度:O(logn)O(\log n),即为排序需要使用的栈空间。