LeetCode #452 Medium

Minimum Number of Arrows to Burst Balloons

Balloons are given as horizontal intervals. An arrow shot upward at x bursts every balloon whose interval contains x. Return the minimum number of arrows needed.

intervalsgreedysorting
Open on LeetCode ↗
02

Intuition

This is Non-overlapping Intervals wearing a different costume, and it carries one extra trap worth naming up front: balloons that touch at a single coordinate. [1,2] and [2,3] are burst by one arrow at x = 2, so a strict comparison counts an arrow that was never needed. Past that, the shape is familiar — sort by end coordinate and shoot at the end of the first balloon still intact, the position that hits the most at once. The answer is the number of groups you cannot merge.

How to spot this pattern

Interval scheduling again: sort by end coordinate and fire an arrow at the end of the first balloon, which pops every balloon overlapping it. Choosing the earliest possible end maximises what a single arrow covers — the same exchange argument as Non-overlapping Intervals.

03

Approach

1

Sort by the end coordinate

Sorting by start fails for the same reason it fails in activity selection: one wide balloon can dominate several narrow ones. Sorting by end works because if you must shoot the earliest-ending balloon at all, shooting at its right edge is optimal — every other balloon that edge reaches would also be reached by any earlier shot, and none is lost.

2

Shoot at the current end and sweep

Take the first balloon's end as the arrow position and count one arrow. Walk the rest: if a balloon's start is at or before that position, the arrow already bursts it, so skip it. Otherwise it is out of range — fire a new arrow and move the position to this balloon's end.

3

Watch the touching case and the overflow

Balloons touching at a single point, like [1,2] and [2,3], are burst by one arrow at x = 2, so the comparison must be start <= pos, not strict. Using < counts an extra arrow. Also, LeetCode's coordinates reach the 32-bit limits, so comparing start > pos directly is safer than computing a difference that could overflow in a fixed-width language. O(n log n) for the sort, O(1) beyond it.

04

Solution & live demo

1class Solution:
2 def findMinArrowShots(self, points):
3 if not points:
4 return 0
5 points.sort(key=lambda x: x[1])
6 arrows = 1
7 pos = points[0][1]
8 for s, e in points[1:]:
9 if s > pos:
10 arrows += 1
11 pos = e
12 # else: the current arrow already bursts it
13 return arrows
05

Common pitfalls

Sorting by start coordinate

✗ Wrong
points.sort(key=lambda x: x[0])
✓ Right
points.sort(key=lambda x: x[1])

Firing at a balloon's start can miss later balloons that a slightly further shot would catch. Anchoring at the earliest end guarantees the arrow is as far right as it can be while still bursting the current balloon.

Using >= for the new-arrow test

✗ Wrong
if s >= pos:
✓ Right
if s > pos:

A balloon starting exactly where the arrow was fired is still touched by it — the ranges are inclusive. The strict test correctly reuses the arrow; >= fires an unnecessary extra one.

Sorting with subtraction on large coordinates

✗ Wrong
sort(points.begin(), points.end(), [](auto&a, auto&b){ return a[1] - b[1] < 0; });
✓ Right
return a[1] < b[1];

Coordinates reach ±2^31, so a[1] - b[1] overflows a 32-bit int and produces an inconsistent comparator — which can corrupt the sort or crash. Compare directly instead of subtracting.

06

Edge cases

Single balloon

One arrow suffices.

All balloons overlapping a common point

One arrow bursts them all.

Balloons touching at exactly one coordinate

Still burst by a single arrow — the <= comparison is what gets this right.

Completely disjoint balloons

Each needs its own arrow, so the answer equals the count.

07

Complexity

Time
O(n log n)
Space
O(1)
Sorting dominates. Structurally identical to Non-overlapping Intervals.