Minimum Number of Arrows to Burst Balloons
Problem
Given a list of points where each point represents the start and end coordinates of a balloon on a horizontal axis, return the minimum number of arrows required to burst all balloons, where an arrow can burst all balloons it passes through.
- 1 ≤ points.length ≤ 10⁵
- points[i].length == 2
- −2³¹ ≤ start_i ≤ end_i ≤ 2³¹ - 1
Example
points = [[10,16],[2,8],[1,6],[7,12]]2Sorting balloons by their end coordinate yields [[1,6],[2,8],[7,12],[10,16]]. The first arrow is shot at position 6, bursting balloons [1,6] and [2,8]. The second arrow is shot at position 12, bursting balloons [7,12] and [10,16]. This greedy choice of shooting at the earliest finishing balloon's end coordinate minimizes the total arrows needed.
Approach
Straightforward Solution
A brute-force approach would check every balloon against every other balloon to find overlaps, resulting in O(n^2) time complexity, which is infeasible for large inputs.
Core Observation
The problem reduces to covering intervals with the minimum number of points (arrows). The key insight is that shooting an arrow at the end of the earliest finishing balloon covers as many overlapping balloons as possible. Because an arrow can be placed anywhere within a balloon’s interval, placing it as late as possible (at the end) maximizes the chance that the same arrow will also intersect future balloons that start later but still overlap that position.
Path to Optimal
Sorting balloons by their end coordinate allows a greedy strategy: always shoot an arrow at the end of the first balloon, then skip all balloons that overlap with this arrow. This reduces the problem to a linear scan after sorting, achieving O(n log n) time due to sorting.
Optimal Approach
Sort the balloons by their end coordinate. Initialize the arrow count to 1 and set the arrow position to the end of the first balloon. Iterate through the balloons; if a balloon starts after the current arrow position, increment the arrow count and update the arrow position to this balloon's end. This ensures each arrow bursts the maximum number of overlapping balloons.
Time
O(n log n)
Sorting the list of balloons by their end coordinate takes O(n log n). The subsequent single pass through the sorted list takes O(n), resulting in overall O(n log n) time.
Space
O(1)
The algorithm uses a constant amount of extra space for counters and pointers, excluding the input array which is sorted in-place.
Pattern Spotlight
Greedy Algorithms (Interval Scheduling)
When covering intervals with points, always pick the earliest finishing interval's end as the point to cover maximum intervals, then skip all intervals overlapping this point before moving to the next uncovered interval.
Solution
| 1 | class Solution: |
| 2 | def findMinArrowShots(self, points: List[List[int]]) -> int: |
| 3 | |
| 4 | points.sort(key=lambda x: x[1]) |
| 5 | |
| 6 | arrows = 1 |
| 7 | arrow_pos = points[0][1] |
| 8 | |
| 9 | for start, end in points: |
| 10 | if start > arrow_pos: |
| 11 | arrows += 1 |
| 12 | arrow_pos = end |
| 13 | |
| 14 | return arrows |
Step-by-Step Solution
Sort Balloons by Their End Coordinates to Enable Greedy Coverage
| 4 | points.sort(key=lambda x: x[1]) |
Objective
To order balloons so that the earliest finishing balloon is considered first, enabling a greedy strategy to minimize arrows.
Key Insight
Sorting by end coordinate ensures that when an arrow is shot at the end of the first balloon, it covers all balloons overlapping that point. This ordering is crucial because it allows the algorithm to make a locally optimal choice that leads to a globally optimal solution.
Interview Quick-Check
Core Logic
Sorting by end coordinate is the foundation of the greedy interval scheduling approach, enabling linear-time coverage after sorting.
Common Pitfalls & Bugs
Sorting by start coordinate instead of end coordinate can lead to suboptimal arrow placement and incorrect results.
Initialize Arrow Count and Position Based on First Balloon
| 6 | arrows = 1 |
| 7 | arrow_pos = points[0][1] |
Objective
To set the initial arrow count and position to cover the first balloon and any overlapping balloons.
Key Insight
Starting with one arrow at the end of the first balloon ensures that the algorithm begins with a valid coverage point. This initial setup is critical because it establishes the baseline for comparing subsequent balloons and deciding when additional arrows are needed.
Interview Quick-Check
Core Logic
Initializing arrow count to 1 and arrow position to the first balloon's end sets the stage for greedy coverage of overlapping balloons.
State & Boundaries
The first balloon must be covered, so starting with one arrow is necessary.
Iterate Through Balloons to Count Additional Arrows Needed
| 9 | for start, end in points: |
| 10 | if start > arrow_pos: |
| 11 | arrows += 1 |
| 12 | arrow_pos = end |
Objective
To scan through the sorted balloons and increment arrow count when a balloon is not covered by the current arrow position.
Key Insight
By comparing each balloon's start coordinate with the current arrow position, the algorithm detects when a balloon lies outside the coverage of the last arrow. At that point, it increments the arrow count and updates the arrow position to the new balloon's end, ensuring minimal arrows are used to cover all balloons.
Interview Quick-Check
Core Logic
The key decision is to shoot a new arrow only when a balloon starts after the current arrow position, guaranteeing minimal arrow usage.
Common Pitfalls & Bugs
Failing to update the arrow position when shooting a new arrow leads to incorrect coverage and overcounting arrows.
State & Boundaries
The loop must consider all balloons to ensure complete coverage.
Return the Total Number of Arrows Required
| 14 | return arrows |
Objective
To output the minimum number of arrows needed to burst all balloons after processing all intervals.
Key Insight
After iterating through all balloons and updating arrow counts as needed, the final arrow count represents the minimal number of arrows required. Returning this value completes the solution.
Interview Quick-Check
Core Logic
Returning the arrow count after processing all balloons provides the minimal number of arrows needed.
Line Analysis
This solution has 3 Critical lines interviewers watch for.
points.sort(key=lambda x: x[1])
Sort the list of balloons by their end coordinate in ascending order.
Sorting by end coordinate enables the greedy strategy to always pick the earliest finishing balloon first, which maximizes the number of balloons burst by a single arrow.
if start > arrow_pos:
Check if the current balloon starts after the last arrow position, indicating it is not covered.
This condition identifies when a new arrow is needed because the current arrow cannot burst this balloon due to non-overlapping intervals.
arrow_pos = end
Update the arrow position to the end of the current balloon for future coverage.
Resetting the arrow position to the current balloon's end maximizes coverage of subsequent overlapping balloons, maintaining the greedy strategy.
arrow_pos = points[0][1]
Set the initial arrow position to the end coordinate of the first balloon.
Placing the arrow at the first balloon's end ensures it bursts that balloon and any overlapping balloons, forming the foundation of the greedy coverage.
arrows += 1
Increment the arrow count as a new arrow is required for the current balloon.
Increasing the arrow count reflects the necessity to cover a balloon that lies outside the coverage of previous arrows, ensuring correctness.
arrows = 1
Initialize the arrow count to 1, assuming at least one arrow is needed.
Starting with one arrow accounts for the first balloon and sets the baseline for counting additional arrows only when necessary.
return arrows
Return the total number of arrows needed to burst all balloons.
After processing all balloons, this value represents the minimal number of arrows required, completing the solution.
for start, end in points:
Iterate through each balloon's start and end coordinates.
This loop examines every balloon to determine if it is covered by the current arrow or requires a new arrow.
Test Your Understanding
Why does shooting an arrow at the end coordinate of the earliest finishing balloon guarantee a minimal number of arrows?
Because shooting at the earliest finishing balloon's end covers all balloons overlapping that point, allowing the algorithm to burst the maximum number of balloons with a single arrow before moving on to the next uncovered balloon.
Related Problems
Greedy pattern
Don't just read it. Drill it.
Reconstruct Minimum Number of Arrows to Burst Balloons from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.
Unlock the Minimum Number of Arrows to Burst Balloons drill