Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Intervals & Greedy

Builds on Interval Scheduling, which you haven’t finished yet.

P2MediumBuilt on Merge Intervals~3 min speedrun

Interval Grouping (Minimum Arrows to Burst Balloons)

Built on
Interval SchedulingCount the groups of intervals that share a point instead of the intervals to remove: shoot at the end of the first interval not yet hit.
The problem

Minimum Number of Arrows to Burst Balloons (LC 452)(opens leetcode.com)

Each balloon is a horizontal span [xstart, xend] on the x-axis, and points lists one span per balloon. The height of a balloon is not given.

An arrow flies straight up from a point x on the x-axis. It bursts every balloon whose span contains x, which means xstart <= x <= xend. You can shoot as many arrows as you like, and an arrow never stops. A balloon whose span only touches x with its edge is also burst.

Return the fewest arrows that burst every balloon.

Example 1
Input:points = [[10,16],[2,8],[1,6],[7,12]]
Output:2
Sorted by end, the balloons are [1,6], [2,8], [7,12] and [10,16]. An arrow at 6 bursts [1,6] and [2,8]. The balloon [7,12] starts after 6, so a second arrow goes at 12 and bursts [7,12] and [10,16].
[10, 16][2, 8][1, 6][7, 12]116

End-Time Groups

intervals ➔ sort by end ➔ one arrow at the end of each interval not yet hit
Spot it when: You must pick the fewest points that touch every interval, or split intervals into the fewest groups that each share a common point.
End-Time Groups
Python
1def find_min_arrow_shots(points: list[list[int]]) -> int:
2 """Minimum Number of Arrows to Burst Balloons: sort by end, shoot at the end of every balloon not yet hit.
3 Time: O(N log N) | Space: O(1) besides the sort
4 """
5 # 1. Sort By End Coordinate:
6 # The balloon that ends first is the safest one to shoot: an arrow at its end hits it, and hits every later
7 # balloon that has already started.
8 points.sort(key=lambda p: p[1])
9 
10 arrows = 0
11 arrow_x = None # x position of the last arrow; None before the first shot
12 
13 for start, end in points:
14 # 2. Shoot Only When The Balloon Is Not Already Hit:
15 # The last arrow hits this balloon when the balloon starts at or before arrow_x. A balloon that only touches
16 # arrow_x still counts as hit, so the test is a strict start > arrow_x.
17 if arrow_x is None or start > arrow_x:
18 arrows += 1
19 arrow_x = end # Shoot at this balloon's end: as far right as it can go and still hit it
20 
21 return arrows
Invariant: Every interval before the current one is hit by one of the arrows, and arrow_x is the end of the last interval that needed a new arrow, so it is the rightmost spot where that arrow still hits it.

Sort the intervals by end and keep arrow_x, the spot of the last arrow. An interval that starts at or before arrow_x is already hit, because every earlier interval ends no later than it does. An interval that starts after arrow_x needs a new arrow, and the best spot for it is its own end, as far right as it can go and still hit it. The trap is the comparison: a balloon that only touches arrow_x is hit, so the test is start > arrow_x, not >=. This is the opposite choice from Interval Scheduling, where touching intervals do not overlap.

See it run

End-Time Groups, step by step

arrow_x 3 · arrows 1

arrow_x 3 · arrows 1

arrow_x 5 · arrows 2

arrow_x 5 · arrows 2

arrow_x 5 · arrows 2

arrow_x 5 · arrows 2

arrow_x 5 · arrows 2

arrow_x 5 · arrows 2

arrow_x 9 · arrows 3

arrow_x 9 · arrows 3 · answer 3

Each column is one x position. Each row of numbers is one balloon, sorted by end. A dash marks the x positions the balloon covers. The word shot or hit replaces the dashes after the decision.

  • start the start of the balloon being checked
  • end the end of the balloon being checked
  • arrow_x the x position of the last arrow

Sort by end. The first balloon [2, 3] is not hit yet, so the first arrow goes to its end: arrow_x = 3.

[4, 5]: start 4 is greater than arrow_x 3, so no arrow reaches this balloon.

Shoot a new arrow at the end 5. arrows becomes 2, and arrow_x = 5.

[1, 6]: start 1 is not greater than arrow_x 5, so the arrow at 5 hits this balloon.

[1, 6] is hit by the same arrow. arrows stays 2.

[5, 8]: start 5 equals arrow_x 5. The balloon only touches the arrow, and it is still hit.

[5, 8] is hit by the same arrow. arrows stays 2.

[7, 9]: start 7 is greater than arrow_x 5, so no arrow reaches this balloon.

Shoot a new arrow at the end 9. arrows becomes 3, and arrow_x = 9.

Done: 3 arrows burst every balloon, so the answer is 3.

[2, 3]
0
1
2shotstart
3shotend
4
5
6
7
8
9
[4, 5]
-
-
[1, 6]
-
-
-
-
-
-
[5, 8]
-
-
-
-
[7, 9]
-
-
-
arrow_x
3arrow_x
1 / 10
Gotchas1 of 3

Gotcha 1 of 3

  1. 1
    The trap

    Gotcha 1: Treating a balloon that only touches the arrow as not hit

    Mistake
    if arrow_x is None or start >= arrow_x:
    Fix
    if arrow_x is None or start > arrow_x:

    What happens: A balloon [2, 3] and an arrow at 2 share the point 2, so the arrow hits it. With >= the code counts a new arrow for it, so every pair of balloons that only touch costs an extra arrow.

    Example: points = [[1, 2], [2, 3], [3, 4], [4, 5]]
    Mistake: returns 4Fix: returns 2
  2. 2

    Gotcha 2: Sorting by start instead of end

    Mistake
    points.sort()
    Fix
    points.sort(key=lambda p: p[1])

    What happens: With the start order, the first balloon can be long, and its end is far to the right of the shorter balloons inside it. The arrow at that far end misses a short balloon that ends earlier, but the code counts it as hit.

    Example: points = [[1, 6], [2, 3], [4, 5]]
    Mistake: returns 1Fix: returns 2
  3. 3

    Gotcha 3: Starting arrow_x at 0

    Mistake
    arrow_x = 0
    Fix
    arrow_x = None

    What happens: Balloon coordinates can be negative. With arrow_x = 0, a balloon that ends below 0 looks already hit, so the first arrow is never shot.

    Example: points = [[-5, -1]]
    Mistake: returns 0Fix: returns 1
Not this one if…
  • Non-overlapping Intervals (LC 435) counts the intervals to remove, and intervals that only touch do not overlap. Here a shared point is a hit, so touching intervals share one arrow. See Interval Scheduling

Deep Dive

What this card adds to Interval Scheduling

This card builds on the Interval Scheduling card. That card sorts by end and counts the intervals to remove. This card sorts by end too, but it counts groups. A group is a set of intervals that all contain one common point.

In Minimum Number of Arrows to Burst Balloons (LC 452), each balloon is a range [start, end] on the x axis. One arrow at position x bursts every balloon with start <= x <= end. The question asks for the fewest arrows, so it asks for the fewest groups.

Both cards use the same sort and the same loop. Two things change. The loop does not remove anything: it shoots a new arrow instead. And the touch rule is reversed. In Interval Scheduling, two intervals that only touch do not overlap. Here, a balloon that only touches the arrow is hit.

Why the arrow goes to the end of the first balloon not yet hit

Look at the balloon that ends first. Some arrow must hit it, so that arrow sits at a position x with start <= x <= end for this balloon. Move the arrow right, up to this balloon's end. It still hits this balloon. It now hits every balloon that the old arrow hit, because every other balloon ends at or after this end, and it already contains the old position x. So an arrow at end is never worse.

After that arrow, a balloon is hit when start <= arrow_x. Since the list is sorted by end, every balloon that comes later ends at or after arrow_x. The only question is where it starts.

The loop does this with arrow_x. A balloon with start > arrow_x starts after the arrow, so no earlier arrow reaches it. The loop adds 1 to arrows and sets arrow_x = end. Every other balloon is already hit, so nothing changes. The starting value is None, so the first balloon always gets an arrow.

Example: in LC 452 with points = [[10, 16], [2, 8], [1, 6], [7, 12]], the sorted order is [1, 6], [2, 8], [7, 12], [10, 16]. The first arrow goes to 6 and hits [1, 6] and [2, 8]. [7, 12] starts at 7, which is greater than 6, so the second arrow goes to 12. It hits [7, 12] and [10, 16]. The answer is 2.

One trace: a nested balloon and a touching balloon

The board replays points = [[1, 6], [2, 3], [4, 5], [5, 8], [7, 9]]. The rows are in sorted order by end, so the first row is [2, 3].

The first arrow goes to 3 and bursts [2, 3]. [4, 5] starts at 4, which is greater than 3, so a second arrow goes to 5. [1, 6] contains [4, 5]. It starts at 1, which is not greater than 5, so the same arrow hits it.

[5, 8] starts at 5, and 5 > 5 is false. The balloon only touches the arrow at 5, and the arrow still hits it. This is the step marked as the trap on the board. Then [7, 9] starts at 7, which is greater than 5, so a third arrow goes to 9. The answer is 3.

The test start >= arrow_x would shoot a new arrow for [5, 8]. For the four balloons [[1, 2], [2, 3], [3, 4], [4, 5]] the code would then return 4 instead of 2, because every pair of neighbours only touches.

The trap: sorting by start, and the cost

Keep the same balloons and sort by start. The order is [1, 6], [2, 3], [4, 5], [5, 8], [7, 9]. The first arrow goes to 6, the end of the long balloon [1, 6]. The code then treats [2, 3] as hit, but 6 is not inside [2, 3]. The code returns 2 instead of 3.

The reason is that the end of the first balloon by start can lie to the right of the short balloons inside it. With the sort by end, arrow_x is always the smallest end among the balloons that are not hit yet, so every later balloon that starts at or before it really contains it.

The sort takes O(N log N) time and the loop takes O(N) time. The loop keeps only arrow_x and arrows, so the extra space is O(1) besides the sort. Start arrow_x at None and not at 0, because coordinates can be negative.

The same idea solves Maximum Length of Pair Chain (LC 646), but there the touch rule is the opposite: a pair can follow another only when its first number is strictly greater than the previous second number. Check the rule of each question before you choose between > and >=.

60-Second Pitch

"One arrow bursts all balloons that contain its position, so I want the fewest groups of balloons that share a point. I sort by end. The balloon that ends first must be hit by some arrow, and I can move that arrow right to the balloon's end without losing any balloon. I keep arrow_x, the position of the last arrow. For each balloon, if start > arrow_x I shoot a new arrow, add 1 to arrows, and set arrow_x = end. Otherwise the last arrow already hits it. The sort costs O(N log N) and the loop costs O(N), with O(1) extra space. I check that a balloon that only touches the arrow is hit, so the test is > and not >=."

Common Questions

Why is the test start > arrow_x and not >=?

The ranges in Minimum Number of Arrows to Burst Balloons (LC 452) include their end points. A balloon [5, 8] and an arrow at 5 share the position 5, so the arrow bursts it. With >=, the code shoots an extra arrow for every balloon that only touches the arrow.

Why sort by end and not by start?

The arrow goes to the end of a balloon. After the sort by end, that end is the smallest end among the balloons not yet hit, so every later balloon that starts at or before it contains it. After a sort by start, the first balloon can be long, and its end can lie to the right of a short balloon inside it.

How is this different from Non-overlapping Intervals (LC 435)?

Both sort by end and use end as the marker. Non-overlapping Intervals (LC 435) counts the intervals to remove, and intervals that only touch do not overlap, so its test is start < prev_end. This card counts the groups, and a touch is a hit, so its test is start > arrow_x.

What are the time and space complexity?

O(N log N) time, because of the sort. The loop is O(N). The extra space is O(1) besides the space the sort uses. The blueprint changes the caller's list by sorting it.

Why start arrow_x at None and not at 0?

Coordinates can be negative. With arrow_x = 0, a balloon such as [-5, -1] has start > arrow_x false, so it looks hit and the first arrow is never shot. None makes the first balloon always get an arrow.

SWE Reasoning

Spot it

  • The question asks for the fewest points that touch every interval.
  • Or it asks to split the intervals into the fewest groups, where every group shares a common point.
  • A shared end point counts as a shared point, so touching intervals can go into one group.
  • The answer is a count, and the positions of the points are not needed.
  • The intervals are balloons, meetings that need one shared moment, or ranges that one action can cover.

The invariant, step by step

  1. Sort points by p[1], which is the end. Set arrows = 0 and arrow_x = None.
  2. For each start, end in the sorted list, test arrow_x is None or start > arrow_x.
  3. If it is true, no earlier arrow reaches this balloon. Shoot a new arrow: arrows += 1.
  4. Set arrow_x = end. This is the rightmost position that still hits this balloon.
  5. If the test is false, the balloon starts at or before arrow_x, and it ends at or after arrow_x. The last arrow hits it, and the loop changes nothing.
  6. Every balloon before the current one is hit by one of the arrows. Return arrows.

Traps

  • Using >= in the test. A balloon that only touches arrow_x is hit. For [[1, 2], [2, 3], [3, 4], [4, 5]] the code returns 4 instead of 2.
  • Sorting by start. The end of a long balloon can lie to the right of the short balloons inside it. For [[1, 6], [2, 3], [4, 5]] the code returns 1 instead of 2.
  • Starting arrow_x at 0. Coordinates can be negative. For [[-5, -1]] the code returns 0 instead of 1. Use None.
  • Moving arrow_x when a balloon is hit. Only a new arrow sets arrow_x. A hit balloon must leave it unchanged.

Practice

Canonical LeetCode:Minimum Number of Arrows to Burst Balloons (LC 452)Maximum Length of Pair Chain (LC 646)
Theory Context•Intervals & Greedy

Builds on Interval Scheduling, which you haven’t finished yet.

P2MediumBuilt on Merge Intervals~3 min speedrun

Interval Grouping (Minimum Arrows to Burst Balloons)

Built on
Interval SchedulingCount the groups of intervals that share a point instead of the intervals to remove: shoot at the end of the first interval not yet hit.
The problem

Minimum Number of Arrows to Burst Balloons (LC 452)(opens leetcode.com)

Each balloon is a horizontal span [xstart, xend] on the x-axis, and points lists one span per balloon. The height of a balloon is not given.

An arrow flies straight up from a point x on the x-axis. It bursts every balloon whose span contains x, which means xstart <= x <= xend. You can shoot as many arrows as you like, and an arrow never stops. A balloon whose span only touches x with its edge is also burst.

Return the fewest arrows that burst every balloon.

Example 1
Input:points = [[10,16],[2,8],[1,6],[7,12]]
Output:2
Sorted by end, the balloons are [1,6], [2,8], [7,12] and [10,16]. An arrow at 6 bursts [1,6] and [2,8]. The balloon [7,12] starts after 6, so a second arrow goes at 12 and bursts [7,12] and [10,16].
[10, 16][2, 8][1, 6][7, 12]116

End-Time Groups

intervals ➔ sort by end ➔ one arrow at the end of each interval not yet hit
Spot it when: You must pick the fewest points that touch every interval, or split intervals into the fewest groups that each share a common point.
End-Time Groups
Python
1def find_min_arrow_shots(points: list[list[int]]) -> int:
2 """Minimum Number of Arrows to Burst Balloons: sort by end, shoot at the end of every balloon not yet hit.
3 Time: O(N log N) | Space: O(1) besides the sort
4 """
5 # 1. Sort By End Coordinate:
6 # The balloon that ends first is the safest one to shoot: an arrow at its end hits it, and hits every later
7 # balloon that has already started.
8 points.sort(key=lambda p: p[1])
9 
10 arrows = 0
11 arrow_x = None # x position of the last arrow; None before the first shot
12 
13 for start, end in points:
14 # 2. Shoot Only When The Balloon Is Not Already Hit:
15 # The last arrow hits this balloon when the balloon starts at or before arrow_x. A balloon that only touches
16 # arrow_x still counts as hit, so the test is a strict start > arrow_x.
17 if arrow_x is None or start > arrow_x:
18 arrows += 1
19 arrow_x = end # Shoot at this balloon's end: as far right as it can go and still hit it
20 
21 return arrows
Invariant: Every interval before the current one is hit by one of the arrows, and arrow_x is the end of the last interval that needed a new arrow, so it is the rightmost spot where that arrow still hits it.

Sort the intervals by end and keep arrow_x, the spot of the last arrow. An interval that starts at or before arrow_x is already hit, because every earlier interval ends no later than it does. An interval that starts after arrow_x needs a new arrow, and the best spot for it is its own end, as far right as it can go and still hit it. The trap is the comparison: a balloon that only touches arrow_x is hit, so the test is start > arrow_x, not >=. This is the opposite choice from Interval Scheduling, where touching intervals do not overlap.

See it run

End-Time Groups, step by step

arrow_x 3 · arrows 1

arrow_x 3 · arrows 1

arrow_x 5 · arrows 2

arrow_x 5 · arrows 2

arrow_x 5 · arrows 2

arrow_x 5 · arrows 2

arrow_x 5 · arrows 2

arrow_x 5 · arrows 2

arrow_x 9 · arrows 3

arrow_x 9 · arrows 3 · answer 3

Each column is one x position. Each row of numbers is one balloon, sorted by end. A dash marks the x positions the balloon covers. The word shot or hit replaces the dashes after the decision.

  • start the start of the balloon being checked
  • end the end of the balloon being checked
  • arrow_x the x position of the last arrow

Sort by end. The first balloon [2, 3] is not hit yet, so the first arrow goes to its end: arrow_x = 3.

[4, 5]: start 4 is greater than arrow_x 3, so no arrow reaches this balloon.

Shoot a new arrow at the end 5. arrows becomes 2, and arrow_x = 5.

[1, 6]: start 1 is not greater than arrow_x 5, so the arrow at 5 hits this balloon.

[1, 6] is hit by the same arrow. arrows stays 2.

[5, 8]: start 5 equals arrow_x 5. The balloon only touches the arrow, and it is still hit.

[5, 8] is hit by the same arrow. arrows stays 2.

[7, 9]: start 7 is greater than arrow_x 5, so no arrow reaches this balloon.

Shoot a new arrow at the end 9. arrows becomes 3, and arrow_x = 9.

Done: 3 arrows burst every balloon, so the answer is 3.

[2, 3]
0
1
2shotstart
3shotend
4
5
6
7
8
9
[4, 5]
-
-
[1, 6]
-
-
-
-
-
-
[5, 8]
-
-
-
-
[7, 9]
-
-
-
arrow_x
3arrow_x
1 / 10
Gotchas1 of 3

Gotcha 1 of 3

  1. 1
    The trap

    Gotcha 1: Treating a balloon that only touches the arrow as not hit

    Mistake
    if arrow_x is None or start >= arrow_x:
    Fix
    if arrow_x is None or start > arrow_x:

    What happens: A balloon [2, 3] and an arrow at 2 share the point 2, so the arrow hits it. With >= the code counts a new arrow for it, so every pair of balloons that only touch costs an extra arrow.

    Example: points = [[1, 2], [2, 3], [3, 4], [4, 5]]
    Mistake: returns 4Fix: returns 2
  2. 2

    Gotcha 2: Sorting by start instead of end

    Mistake
    points.sort()
    Fix
    points.sort(key=lambda p: p[1])

    What happens: With the start order, the first balloon can be long, and its end is far to the right of the shorter balloons inside it. The arrow at that far end misses a short balloon that ends earlier, but the code counts it as hit.

    Example: points = [[1, 6], [2, 3], [4, 5]]
    Mistake: returns 1Fix: returns 2
  3. 3

    Gotcha 3: Starting arrow_x at 0

    Mistake
    arrow_x = 0
    Fix
    arrow_x = None

    What happens: Balloon coordinates can be negative. With arrow_x = 0, a balloon that ends below 0 looks already hit, so the first arrow is never shot.

    Example: points = [[-5, -1]]
    Mistake: returns 0Fix: returns 1
Not this one if…
  • Non-overlapping Intervals (LC 435) counts the intervals to remove, and intervals that only touch do not overlap. Here a shared point is a hit, so touching intervals share one arrow. See Interval Scheduling

Deep Dive

What this card adds to Interval Scheduling

This card builds on the Interval Scheduling card. That card sorts by end and counts the intervals to remove. This card sorts by end too, but it counts groups. A group is a set of intervals that all contain one common point.

In Minimum Number of Arrows to Burst Balloons (LC 452), each balloon is a range [start, end] on the x axis. One arrow at position x bursts every balloon with start <= x <= end. The question asks for the fewest arrows, so it asks for the fewest groups.

Both cards use the same sort and the same loop. Two things change. The loop does not remove anything: it shoots a new arrow instead. And the touch rule is reversed. In Interval Scheduling, two intervals that only touch do not overlap. Here, a balloon that only touches the arrow is hit.

Why the arrow goes to the end of the first balloon not yet hit

Look at the balloon that ends first. Some arrow must hit it, so that arrow sits at a position x with start <= x <= end for this balloon. Move the arrow right, up to this balloon's end. It still hits this balloon. It now hits every balloon that the old arrow hit, because every other balloon ends at or after this end, and it already contains the old position x. So an arrow at end is never worse.

After that arrow, a balloon is hit when start <= arrow_x. Since the list is sorted by end, every balloon that comes later ends at or after arrow_x. The only question is where it starts.

The loop does this with arrow_x. A balloon with start > arrow_x starts after the arrow, so no earlier arrow reaches it. The loop adds 1 to arrows and sets arrow_x = end. Every other balloon is already hit, so nothing changes. The starting value is None, so the first balloon always gets an arrow.

Example: in LC 452 with points = [[10, 16], [2, 8], [1, 6], [7, 12]], the sorted order is [1, 6], [2, 8], [7, 12], [10, 16]. The first arrow goes to 6 and hits [1, 6] and [2, 8]. [7, 12] starts at 7, which is greater than 6, so the second arrow goes to 12. It hits [7, 12] and [10, 16]. The answer is 2.

One trace: a nested balloon and a touching balloon

The board replays points = [[1, 6], [2, 3], [4, 5], [5, 8], [7, 9]]. The rows are in sorted order by end, so the first row is [2, 3].

The first arrow goes to 3 and bursts [2, 3]. [4, 5] starts at 4, which is greater than 3, so a second arrow goes to 5. [1, 6] contains [4, 5]. It starts at 1, which is not greater than 5, so the same arrow hits it.

[5, 8] starts at 5, and 5 > 5 is false. The balloon only touches the arrow at 5, and the arrow still hits it. This is the step marked as the trap on the board. Then [7, 9] starts at 7, which is greater than 5, so a third arrow goes to 9. The answer is 3.

The test start >= arrow_x would shoot a new arrow for [5, 8]. For the four balloons [[1, 2], [2, 3], [3, 4], [4, 5]] the code would then return 4 instead of 2, because every pair of neighbours only touches.

The trap: sorting by start, and the cost

Keep the same balloons and sort by start. The order is [1, 6], [2, 3], [4, 5], [5, 8], [7, 9]. The first arrow goes to 6, the end of the long balloon [1, 6]. The code then treats [2, 3] as hit, but 6 is not inside [2, 3]. The code returns 2 instead of 3.

The reason is that the end of the first balloon by start can lie to the right of the short balloons inside it. With the sort by end, arrow_x is always the smallest end among the balloons that are not hit yet, so every later balloon that starts at or before it really contains it.

The sort takes O(N log N) time and the loop takes O(N) time. The loop keeps only arrow_x and arrows, so the extra space is O(1) besides the sort. Start arrow_x at None and not at 0, because coordinates can be negative.

The same idea solves Maximum Length of Pair Chain (LC 646), but there the touch rule is the opposite: a pair can follow another only when its first number is strictly greater than the previous second number. Check the rule of each question before you choose between > and >=.

60-Second Pitch

"One arrow bursts all balloons that contain its position, so I want the fewest groups of balloons that share a point. I sort by end. The balloon that ends first must be hit by some arrow, and I can move that arrow right to the balloon's end without losing any balloon. I keep arrow_x, the position of the last arrow. For each balloon, if start > arrow_x I shoot a new arrow, add 1 to arrows, and set arrow_x = end. Otherwise the last arrow already hits it. The sort costs O(N log N) and the loop costs O(N), with O(1) extra space. I check that a balloon that only touches the arrow is hit, so the test is > and not >=."

Common Questions

Why is the test start > arrow_x and not >=?

The ranges in Minimum Number of Arrows to Burst Balloons (LC 452) include their end points. A balloon [5, 8] and an arrow at 5 share the position 5, so the arrow bursts it. With >=, the code shoots an extra arrow for every balloon that only touches the arrow.

Why sort by end and not by start?

The arrow goes to the end of a balloon. After the sort by end, that end is the smallest end among the balloons not yet hit, so every later balloon that starts at or before it contains it. After a sort by start, the first balloon can be long, and its end can lie to the right of a short balloon inside it.

How is this different from Non-overlapping Intervals (LC 435)?

Both sort by end and use end as the marker. Non-overlapping Intervals (LC 435) counts the intervals to remove, and intervals that only touch do not overlap, so its test is start < prev_end. This card counts the groups, and a touch is a hit, so its test is start > arrow_x.

What are the time and space complexity?

O(N log N) time, because of the sort. The loop is O(N). The extra space is O(1) besides the space the sort uses. The blueprint changes the caller's list by sorting it.

Why start arrow_x at None and not at 0?

Coordinates can be negative. With arrow_x = 0, a balloon such as [-5, -1] has start > arrow_x false, so it looks hit and the first arrow is never shot. None makes the first balloon always get an arrow.

SWE Reasoning

Spot it

  • The question asks for the fewest points that touch every interval.
  • Or it asks to split the intervals into the fewest groups, where every group shares a common point.
  • A shared end point counts as a shared point, so touching intervals can go into one group.
  • The answer is a count, and the positions of the points are not needed.
  • The intervals are balloons, meetings that need one shared moment, or ranges that one action can cover.

The invariant, step by step

  1. Sort points by p[1], which is the end. Set arrows = 0 and arrow_x = None.
  2. For each start, end in the sorted list, test arrow_x is None or start > arrow_x.
  3. If it is true, no earlier arrow reaches this balloon. Shoot a new arrow: arrows += 1.
  4. Set arrow_x = end. This is the rightmost position that still hits this balloon.
  5. If the test is false, the balloon starts at or before arrow_x, and it ends at or after arrow_x. The last arrow hits it, and the loop changes nothing.
  6. Every balloon before the current one is hit by one of the arrows. Return arrows.

Traps

  • Using >= in the test. A balloon that only touches arrow_x is hit. For [[1, 2], [2, 3], [3, 4], [4, 5]] the code returns 4 instead of 2.
  • Sorting by start. The end of a long balloon can lie to the right of the short balloons inside it. For [[1, 6], [2, 3], [4, 5]] the code returns 1 instead of 2.
  • Starting arrow_x at 0. Coordinates can be negative. For [[-5, -1]] the code returns 0 instead of 1. Use None.
  • Moving arrow_x when a balloon is hit. Only a new arrow sets arrow_x. A hit balloon must leave it unchanged.

Practice

Canonical LeetCode:Minimum Number of Arrows to Burst Balloons (LC 452)Maximum Length of Pair Chain (LC 646)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•