题目描述
示例
思路、算法及代码实现
首先我们随机发射出一支箭,看看它能射中几个气球,如图1-1所示,此时能射中黄、蓝、绿这三个气球。
其次,我们将箭的发射位置(即路径)向右移,原本完好的红色气球被引爆,此时我们可以射中红、黄、蓝、绿,这四个气球了。在这个情况下,人性的贪念就暴露出来了,我们想继续向右移,希望能射中更多的气球,但不希望我们原本能射中的气球变少,这就是所谓的贪心算法。
基于我们的贪心,我们就想要知道在保证能射中的气球数量最多的情况下,能右移的最远距离。如图1-3所示,这个最远距离是到蓝色气球的右端为止。更一般来说,这个终止处是原本能被引爆的所有气球中的右边界位置最靠左的那个右边界位置。再往右移一点点的话,一次性能射中的气球就变少了。
因此,我们可以断定,一定存在一种最优(射出的箭数最少)的射出位置,使得一次性射中的气球数最多,这个最优位置就是某个气球的右边界。
我们知道,每多一个与当前气球无法重合的气球时,就得多射出一支箭来引爆它,所以我们要搞清楚气球什么时候重合(即能一箭多雕),什么时候不能重合。
刚刚说了,能右移的最远处是右边界最靠左的那个气球的右边界处,怎么找右边界最靠左呢?答案是将所有气球的右边界按升序排序。
排完序之后,下面来考虑两个区间什么时候重合,比如现在有区间x[x0,x1]和区间y[y0,y1],因为排完序了,所以一定有x1<=y1,若要两个区间重合,只要满足x1>=y0即可。反之,如果y0>x1,则表示这两个区间不重合,得增加一支箭,而且得将新增加的那支箭的射出位置移到区间y的右边界y1处,再向右找重合的区间。
因为结果要求输出需要的箭的个数,所以我们重点在关注什么时候两个区间不重合。
class Solution:
def findMinArrowShots(self, points: List[List[int]]) -> int:
# 如果没有气球,那就不需要箭
if not points:
return 0
# 对气球进行排序,为了满足区间重合的半个条件:下一个区间的右端大于当前区间的右端,所以按右端的升序排,
points.sort(key=lambda point: point[1])
# 箭的发射位置pos ,初始时pos在排序后第一个区间的右端
pos = points[0][1]
# 所需箭的数量ans
ans = 1
for balloon in points:
if balloon[0] > pos: # 如果下一个气球的左端大于当前气球的右端(即箭的发射位置)的话,则不重合
ans += 1 # 所需箭的数量增加1
pos = balloon[1] # 新增加的箭的发射位置更新为下一个气球的右端
return ans