On This Page

What Is a Greedy Algorithm?

A greedy algorithm makes a sequence of choices, each time picking the option that looks best right now โ€” without looking ahead or reconsidering past decisions. Once a choice is made, it's final. No backtracking, no "what if," no regret.

The Everyday Analogy

Imagine you're at a buffet and you want to fill your plate with the most delicious food. A greedy strategy: always take the tastiest-looking item you can see right now. This works surprisingly well at most buffets โ€” the lobster is always a better pick than the dinner roll. But it fails if the dessert table is hidden around a corner and you've already piled your plate high with appetizers. Greedy works when you can see enough of the problem to make good local decisions.

A Brief History

Greedy algorithms have been around almost as long as algorithms themselves. Kruskal's algorithm for minimum spanning trees was published in 1956. Dijkstra's shortest path algorithm followed in 1959 โ€” Edsger Dijkstra reportedly invented it in about 20 minutes while sitting at a cafรฉ in Amsterdam. Huffman coding (1952) was developed by David Huffman as a term paper for an MIT class when his professor offered an A to anyone who could find the optimal prefix-free code. Huffman succeeded where his professor and the information theorist Claude Shannon had not.

The term "greedy" was formalized in the 1970s as part of the broader study of matroid theory โ€” a mathematical framework that precisely characterizes when greedy algorithms give optimal results.

Two Properties That Must Hold

Greedy only works when the problem has two properties:

Greedy vs Dynamic Programming

DP considers all options and picks the best after evaluating everything. Greedy picks one option immediately and never looks back. When greedy works, it's typically faster and simpler than DP โ€” O(n log n) vs O(nยฒ) in many cases. But greedy is less general. It gives the right answer for fewer problems. The key question is always: can I prove that the locally best choice never needs revision?

Where Greedy Shows Up

โš  Greedy Doesn't Always Work The tricky part: greedy gives the right answer for some problems and the wrong answer for others, and it's not always obvious which. Making change with coins {1, 3, 4} for amount 6: greedy picks 4+1+1 (3 coins), but optimal is 3+3 (2 coins). You need to prove correctness โ€” usually by exchange argument (showing you can swap any non-greedy choice for the greedy choice without worsening the solution) or contradiction (assuming greedy is suboptimal and deriving an impossibility).

How It Works

The Activity Selection Problem

The textbook example, and the one that makes greedy click. You have a set of activities, each with a start and finish time. You want to attend as many as possible, but they can't overlap. Here's the greedy approach:

  1. Sort all activities by their finish time (earliest finish first)
  2. Pick the first activity (it finishes earliest, leaving the most room for everything else)
  3. For each remaining activity: if it starts at or after the last picked activity finishes, take it
  4. Skip anything that overlaps with your current selection

Why sorting by finish time works: Because finishing early leaves the maximum remaining time for future activities. Intuitively, an activity that finishes at 2pm leaves more room than one that finishes at 5pm, even if the 5pm one started earlier. Sorting by start time or duration would give wrong answers โ€” you can construct counterexamples for both.

The proof sketch (exchange argument): Take any optimal solution. If its first activity doesn't finish earliest, swap it with the earliest-finishing activity. The swap doesn't create conflicts (the replacement finishes sooner, so it can't overlap with the second activity). Repeat this argument for each subsequent activity. You end up with the greedy solution, which has the same number of activities as the optimal one. Therefore greedy is optimal.

The Greedy Template

  1. Sort or organize the input by some criterion (this is the greedy choice โ€” the criterion matters enormously)
  2. Initialize your solution (empty set, counter, etc.)
  3. Iterate through items in sorted order
  4. For each item: if it's compatible with your current solution, include it
  5. Return the solution โ€” no backtracking needed

Proving Greedy is Correct

You can't just hope greedy works. You need a proof. Two standard techniques:

In interviews, you usually don't need to write a formal proof, but you should be able to articulate why the greedy choice works. "It seems right" is not good enough. "Finishing earlier leaves more room" is.

When Greedy Fails โ€” And How to Tell

The fastest way to check: try to find a small counterexample. If you can construct an input where greedy gives a suboptimal answer, it fails. Coin change with {1, 3, 4} and amount 6 is the classic counterexample. If you can't find a counterexample after trying a few cases, the greedy approach might work โ€” but "might" isn't "does." The proof matters.

Interactive Visualization

Watch greedy activity selection in action. Activities are sorted by finish time. The algorithm picks the earliest-finishing activity, then skips anything that conflicts. Green = selected, grey = skipped due to overlap.

Activity Selection

Common Mistakes

โš ๏ธ Mistake #1: Assuming greedy works without proof The most dangerous mistake. Greedy solutions look elegant and obvious โ€” but "obvious" is often wrong. Coin change is the classic trap: greedy works for US coins but fails for arbitrary denominations. Always either prove correctness or find a counterexample. "It passed the sample test cases" is not a proof.
โš ๏ธ Mistake #2: Sorting by the wrong criterion Activity selection works when you sort by finish time. Sort by start time and you'd pick the activity that starts earliest but might run all day. Sort by duration and you'd pick the shortest activities that might conflict with each other. The choice of sort key IS the greedy strategy, and getting it wrong gives a correct-looking but wrong algorithm.
โš ๏ธ Mistake #3: Applying greedy to 0/1 knapsack Fractional knapsack (take fractions of items) is solvable greedily โ€” sort by value/weight ratio and take as much of the best item as you can. But 0/1 knapsack (take whole items or nothing) is NOT greedy-solvable. A 10kg gold bar worth $1000 beats two 6kg items worth $600 each, even though the 6kg items have a better per-kilo ratio. You need DP for 0/1 knapsack.
โš ๏ธ Mistake #4: Forgetting to handle ties When two items have the same greedy criterion (same finish time, same value/weight ratio), the tiebreaker matters. Usually you need a secondary sort: for intervals, break ties by start time. Inconsistent tie handling can produce different results on different runs, making debugging a nightmare.
โš ๏ธ Mistake #5: Not considering edge cases with empty input or single elements Greedy algorithms often iterate through sorted data and compare to a "last selected" item. If the input is empty or has one element, that comparison can fail. Always handle len(input) <= 1 before entering the greedy loop.

Complexity Analysis

Greedy algorithms are usually fast. The bottleneck is almost always the sorting step โ€” the greedy selection itself is typically a linear scan.

AlgorithmTimeSpaceNotes
Activity SelectionO(n log n)O(n)Sort by finish time, then linear scan
Fractional KnapsackO(n log n)O(1)Sort by value/weight ratio
Huffman CodingO(n log n)O(n)Priority queue (min-heap) for n symbols
Jump GameO(n)O(1)Track farthest reachable index
Minimum PlatformsO(n log n)O(n)Sort arrivals and departures separately
Dijkstra (min-heap)O((V+E) log V)O(V)Greedy + priority queue
Kruskal's MSTO(E log E)O(V)Sort edges + Union-Find

Implementation

Activity Selection

Python

def activity_selection(activities):
    """Select maximum non-overlapping activities.
    activities = list of (start, finish) tuples.
    Greedy strategy: always pick the one that finishes earliest."""
    if not activities:
        return []

    # Sort by finish time โ€” finishing early leaves more room
    sorted_acts = sorted(activities, key=lambda x: x[1])
    selected = [sorted_acts[0]]

    for start, finish in sorted_acts[1:]:
        # Only pick activities that start at or after the last one ends
        if start >= selected[-1][1]:
            selected.append((start, finish))

    return selected

# Example
activities = [(1, 4), (3, 5), (0, 6), (5, 7), (3, 9), (5, 9),
              (6, 10), (8, 11), (8, 12), (2, 14), (12, 16)]
result = activity_selection(activities)
print(f"Selected {len(result)} activities: {result}")
# Selected 4 activities: [(1, 4), (5, 7), (8, 11), (12, 16)]
      

Non-Overlapping Intervals (Minimum Removals)

Python

def erase_overlap_intervals(intervals):
    """Find the minimum number of intervals to REMOVE so the
    rest don't overlap. This is the complement of activity selection:
    max non-overlapping = n - min removals.
    
    Same greedy idea: sort by finish time, keep non-overlapping ones."""
    if not intervals:
        return 0

    intervals.sort(key=lambda x: x[1])  # Sort by end time
    kept = 1              # Keep the first interval
    last_end = intervals[0][1]

    for start, end in intervals[1:]:
        if start >= last_end:
            # No overlap โ€” keep this interval
            kept += 1
            last_end = end
        # else: overlaps โ€” skip it (implicitly "remove" it)

    return len(intervals) - kept  # Total - kept = removed
      

Fractional Knapsack

Python

def fractional_knapsack(items, capacity):
    """Maximize value with a weight limit. Can take fractions.
    items = list of (weight, value) tuples.
    Greedy: sort by value/weight ratio, take as much of the
    best items as possible."""
    # Sort by value-per-weight ratio, highest first
    items_sorted = sorted(items, key=lambda x: x[1]/x[0], reverse=True)

    total_value = 0.0
    remaining = capacity

    for weight, value in items_sorted:
        if remaining <= 0:
            break
        if weight <= remaining:
            # Take the whole item
            total_value += value
            remaining -= weight
        else:
            # Take a fraction โ€” this is why greedy works here
            # (you can't do this in 0/1 knapsack)
            fraction = remaining / weight
            total_value += value * fraction
            remaining = 0

    return total_value
      
JavaScript

function fractionalKnapsack(items, capacity) {
  // Sort by value-per-weight ratio, highest first
  items.sort((a, b) => (b.value / b.weight) - (a.value / a.weight));

  let totalValue = 0;
  let remaining = capacity;

  for (const item of items) {
    if (remaining <= 0) break;

    if (item.weight <= remaining) {
      totalValue += item.value;
      remaining -= item.weight;
    } else {
      totalValue += item.value * (remaining / item.weight);
      remaining = 0;
    }
  }
  return totalValue;
}
      

Jump Game (Can You Reach the End?)

Python

def can_jump(nums):
    """Can you reach the last index? nums[i] = max jump length from i.
    Greedy: track the farthest index reachable so far.
    If at any point i > farthest, we're stuck."""
    farthest = 0
    for i in range(len(nums)):
        if i > farthest:
            return False  # Can't even reach this position
        # Greedily extend our reach as far as possible
        farthest = max(farthest, i + nums[i])
        if farthest >= len(nums) - 1:
            return True  # Early exit: can already reach the end
    return True
      

Jump Game II (Minimum Jumps to Reach End)

Python

def jump(nums):
    """Minimum number of jumps to reach the last index.
    Greedy BFS: each 'level' is a range of indices reachable
    with the same number of jumps. Expand the range greedily."""
    if len(nums) <= 1:
        return 0

    jumps = 0
    current_end = 0    # Farthest we can reach with 'jumps' jumps
    farthest = 0       # Farthest we can reach with 'jumps + 1' jumps

    for i in range(len(nums) - 1):
        farthest = max(farthest, i + nums[i])

        if i == current_end:
            # We've explored all positions reachable with current jumps
            jumps += 1
            current_end = farthest
            if current_end >= len(nums) - 1:
                break

    return jumps
      

Activity Selection (JavaScript)

JavaScript

function activitySelection(activities) {
  /**
   * Select maximum non-overlapping activities.
   * activities = array of {start, finish} objects.
   * Greedy: sort by finish time, always pick the earliest finisher.
   */
  if (activities.length === 0) return [];

  // Sort by finish time โ€” earliest finisher first
  const sorted = [...activities].sort((a, b) => a.finish - b.finish);
  const selected = [sorted[0]];

  for (let i = 1; i < sorted.length; i++) {
    // Only pick if this activity starts at or after the last one ended
    if (sorted[i].start >= selected[selected.length - 1].finish) {
      selected.push(sorted[i]);
    }
  }
  return selected;
}

// Example
const acts = [
  {start: 1, finish: 4}, {start: 3, finish: 5}, {start: 0, finish: 6},
  {start: 5, finish: 7}, {start: 3, finish: 9}, {start: 5, finish: 9},
  {start: 6, finish: 10}, {start: 8, finish: 11}, {start: 8, finish: 12},
  {start: 2, finish: 14}, {start: 12, finish: 16}
];
console.log(activitySelection(acts));
// [{start:1,finish:4}, {start:5,finish:7}, {start:8,finish:11}, {start:12,finish:16}]
      

Jump Game (JavaScript)

JavaScript

function canJump(nums) {
  /**
   * Can you reach the last index?
   * nums[i] = max jump length from position i.
   * Greedy: track the farthest index reachable so far.
   */
  let farthest = 0;
  for (let i = 0; i < nums.length; i++) {
    if (i > farthest) return false; // Can't reach this position
    farthest = Math.max(farthest, i + nums[i]);
    if (farthest >= nums.length - 1) return true; // Early exit
  }
  return true;
}

function minJumps(nums) {
  /**
   * Minimum jumps to reach the end.
   * Greedy BFS: each "level" is a range of reachable indices.
   */
  if (nums.length <= 1) return 0;
  let jumps = 0, currentEnd = 0, farthest = 0;

  for (let i = 0; i < nums.length - 1; i++) {
    farthest = Math.max(farthest, i + nums[i]);
    if (i === currentEnd) {
      jumps++;
      currentEnd = farthest;
      if (currentEnd >= nums.length - 1) break;
    }
  }
  return jumps;
}
      

Huffman Coding (Optimal Compression)

Python

import heapq

def huffman_coding(freq):
    """Build a Huffman tree for optimal prefix-free encoding.
    freq = dict of {symbol: frequency}.
    Greedy: always merge the two lowest-frequency nodes.
    
    Returns a dict of {symbol: binary_code_string}."""
    # Priority queue of (frequency, unique_id, node)
    # unique_id prevents comparison of nodes when frequencies tie
    heap = [(f, i, symbol) for i, (symbol, f) in enumerate(freq.items())]
    heapq.heapify(heap)
    uid = len(heap)

    # Merge nodes until one tree remains
    while len(heap) > 1:
        f1, _, left = heapq.heappop(heap)
        f2, _, right = heapq.heappop(heap)
        merged = (left, right)  # Internal node
        heapq.heappush(heap, (f1 + f2, uid, merged))
        uid += 1

    # Extract codes by traversing the tree
    codes = {}
    def traverse(node, code=""):
        if isinstance(node, str):
            codes[node] = code or "0"  # Single-symbol edge case
            return
        left, right = node
        traverse(left, code + "0")
        traverse(right, code + "1")

    if heap:
        traverse(heap[0][2])
    return codes

# Example
frequencies = {"a": 45, "b": 13, "c": 12, "d": 16, "e": 9, "f": 5}
codes = huffman_coding(frequencies)
for symbol, code in sorted(codes.items()):
    print(f"  '{symbol}': {code}")
# 'a' gets the shortest code (highest frequency)
# 'f' gets the longest code (lowest frequency)
      

Real-World Applications

1. File Compression (Huffman Coding)

Every time you create a ZIP file, JPEG image, or MP3 audio file, Huffman coding (or a descendant like arithmetic coding) is at work. The core idea: assign shorter binary codes to frequent symbols and longer codes to rare ones. A file where 'e' appears 1000 times and 'z' appears twice? Give 'e' a 2-bit code and 'z' a 12-bit code. Huffman's greedy strategy โ€” always merge the two least-frequent symbols โ€” provably produces the optimal prefix-free code. DEFLATE (used in gzip, PNG, and ZIP) combines Huffman coding with LZ77 dictionary compression.

2. Network Routing (Dijkstra's Algorithm)

When your packet travels across the internet, routers use variants of Dijkstra's algorithm (a greedy shortest-path algorithm) to decide where to forward it. OSPF (Open Shortest Path First), one of the most widely deployed routing protocols, runs Dijkstra on a weighted graph of network links. Each router builds a complete map of the network topology and greedily extends shortest paths by always processing the nearest unvisited router next. BGP (Border Gateway Protocol), which routes between large networks, uses a different greedy strategy based on path attributes and policy preferences.

3. Operating System CPU Scheduling

The Shortest Job First (SJF) CPU scheduling algorithm is greedy: always run the process with the shortest expected burst time next. This minimizes average waiting time. Real OS schedulers (Linux's CFS, Windows' dispatcher) use more sophisticated versions, but the core insight is greedy โ€” prioritize jobs that free up the CPU fastest. The Shortest Remaining Time First (SRTF) variant is preemptive: if a new job arrives with a shorter remaining time than the current one, switch immediately.

4. Conference Room Scheduling

You're building a conference room booking system. Given a list of meeting requests, how many rooms do you need so that no two overlapping meetings share a room? This is the "minimum platforms" / "meeting rooms" problem โ€” one of the most practical greedy algorithms out there.

Python

import heapq

def min_meeting_rooms(meetings):
    """Find the minimum number of conference rooms needed.
    meetings = list of (start, end) tuples.
    
    Greedy with a min-heap: track when each room becomes free.
    When a new meeting starts, if the earliest-freeing room is
    available, reuse it. Otherwise, allocate a new room.
    
    The heap size at any point = number of rooms in use."""
    if not meetings:
        return 0

    # Sort by start time โ€” process meetings in chronological order
    meetings.sort(key=lambda x: x[0])

    # Min-heap of end times (when each room becomes free)
    rooms = []
    heapq.heappush(rooms, meetings[0][1])  # First meeting gets a room

    for start, end in meetings[1:]:
        if rooms[0] <= start:
            # Earliest-freeing room is available โ€” reuse it
            heapq.heapreplace(rooms, end)  # Pop old end, push new end
        else:
            # All rooms are busy โ€” need a new one
            heapq.heappush(rooms, end)

    return len(rooms)

# Example: a busy Tuesday
meetings = [
    (9, 10),    # Standup
    (9, 11),    # Planning session (overlaps with standup)
    (10, 12),   # Design review
    (11, 13),   # Lunch meeting
    (13, 14),   # 1-on-1
    (14, 15),   # Sprint retro
]

rooms_needed = min_meeting_rooms(meetings)
print(f"Need {rooms_needed} rooms")  # Need 2 rooms
# 9-10 and 9-11 overlap โ†’ 2 rooms
# From 11 onward, only 1 room is needed at a time
      

This exact algorithm powers conference room displays, Calendly-style scheduling tools, and operating system resource allocation. The heap tracks which resources become available soonest โ€” a pattern that shows up anywhere you're managing a pool of reusable resources (database connections, worker threads, parking spots).

Meeting Rooms (JavaScript)

JavaScript

function minMeetingRooms(meetings) {
  /**
   * Minimum conference rooms needed. O(n log n) time.
   * 
   * Alternative approach: event-based sweep line.
   * Mark each start as +1 room needed, each end as -1.
   * Sort all events. The peak concurrent meetings = answer.
   */
  if (meetings.length === 0) return 0;

  const events = [];
  for (const [start, end] of meetings) {
    events.push([start, 1]);   // +1 room at start
    events.push([end, -1]);    // -1 room at end
  }

  // Sort by time. If tied, process ends (-1) before starts (+1)
  // so that a meeting ending at 10:00 frees the room before
  // a meeting starting at 10:00 claims it.
  events.sort((a, b) => a[0] - b[0] || a[1] - b[1]);

  let rooms = 0, maxRooms = 0;
  for (const [, delta] of events) {
    rooms += delta;
    maxRooms = Math.max(maxRooms, rooms);
  }
  return maxRooms;
}

console.log(minMeetingRooms([[9,10],[9,11],[10,12],[11,13],[13,14]]));
// 2 (9-10 and 9-11 overlap)
      

Huffman Coding (JavaScript)

JavaScript

function huffmanCoding(freq) {
  /**
   * Build Huffman codes from a frequency map.
   * freq = { 'a': 45, 'b': 13, 'c': 12, ... }
   * Returns { 'a': '0', 'b': '101', ... }
   * 
   * Uses a simple sorted-array priority queue (fine for small alphabets).
   * For large alphabets, use a proper min-heap.
   */
  // Build initial nodes sorted by frequency
  let nodes = Object.entries(freq)
    .map(([sym, f]) => ({ freq: f, symbol: sym }))
    .sort((a, b) => a.freq - b.freq);

  if (nodes.length === 1) {
    return { [nodes[0].symbol]: '0' };
  }

  // Merge two lowest-frequency nodes until one tree remains
  while (nodes.length > 1) {
    const left = nodes.shift();
    const right = nodes.shift();
    const merged = { freq: left.freq + right.freq, left, right };

    // Insert merged node in sorted position
    let i = 0;
    while (i < nodes.length && nodes[i].freq <= merged.freq) i++;
    nodes.splice(i, 0, merged);
  }

  // Traverse tree to extract codes
  const codes = {};
  function traverse(node, code) {
    if (node.symbol) {
      codes[node.symbol] = code || '0';
      return;
    }
    if (node.left) traverse(node.left, code + '0');
    if (node.right) traverse(node.right, code + '1');
  }
  traverse(nodes[0], '');
  return codes;
}

const codes = huffmanCoding({ a: 45, b: 13, c: 12, d: 16, e: 9, f: 5 });
console.log(codes);
// 'a' gets shortest code (highest frequency), 'f' gets longest
      

Worked Example: Activity Selection Step by Step

Let's trace through activity selection with this input:

Activities: (1,4) (3,5) (0,6) (5,7) (3,9) (6,10) (8,11) (12,16)
    

Step 1: Sort by finish time. Already sorted above. Earliest finisher is (1,4).

Step 2: Pick (1,4). Selected = [(1,4)]. Last finish = 4.

Step 3: Check (3,5). Start 3 < last finish 4 โ†’ overlaps. Skip.

Step 4: Check (0,6). Start 0 < 4 โ†’ overlaps. Skip.

Step 5: Check (5,7). Start 5 โ‰ฅ 4 โ†’ no overlap! Pick it. Selected = [(1,4), (5,7)]. Last finish = 7.

Step 6: Check (3,9). Start 3 < 7 โ†’ overlaps. Skip.

Step 7: Check (6,10). Start 6 < 7 โ†’ overlaps. Skip.

Step 8: Check (8,11). Start 8 โ‰ฅ 7 โ†’ pick it! Selected = [(1,4), (5,7), (8,11)]. Last finish = 11.

Step 9: Check (12,16). Start 12 โ‰ฅ 11 โ†’ pick it! Selected = [(1,4), (5,7), (8,11), (12,16)].

Result: 4 activities selected. You can verify this is optimal โ€” no selection of 5 non-overlapping activities exists for this input. The greedy strategy (earliest finish time) leaves maximum room for future activities at every step.

Interview Patterns

๐Ÿ’ก Greedy vs DP โ€” How to Decide If the problem asks for "minimum number of" or "maximum number of" and you can prove that the locally best choice never needs revision, use greedy. If you're not sure whether greedy works, try a small counterexample (3-5 elements). If you find one where greedy gives the wrong answer, use DP instead. In interviews, briefly explain why greedy works or acknowledge that you'd need to verify it.
๐Ÿ’ก The Sorting Step Matters Most greedy problems boil down to: "what do I sort by?" Activity selection sorts by finish time. Fractional knapsack sorts by value/weight ratio. Meeting rooms sorts by start time. Assigning cookies to children sorts both arrays. Getting the sort criterion right is 80% of solving greedy problems.
๐Ÿ’ก Classic Trap: Coin Change "Find minimum coins for amount X." Greedy (always pick the largest coin that fits) works for US coins {1, 5, 10, 25} but fails for {1, 3, 4} with amount 6. Interviewers use this to test whether you recognize when greedy breaks down. If they ask "can you do this greedily?" the answer is "only for certain coin systems โ€” in general, you need DP."
๐Ÿ’ก Interval Problems Are Almost Always Greedy If the problem involves intervals (time ranges, line segments), sort by some endpoint and scan. Merge intervals: sort by start, merge overlapping ones. Non-overlapping intervals: sort by end, count non-overlapping. Insert interval: find the insertion point and merge. The pattern is consistent: sort โ†’ scan โ†’ merge/count.
๐Ÿ’ก Heap-Assisted Greedy Some greedy problems need "the best available option" at each step, which changes as you process items. A priority queue (min-heap or max-heap) gives O(log n) access to the best option. Meeting rooms uses a min-heap of end times. Task scheduler uses a max-heap of task frequencies. Merge k sorted lists uses a min-heap of list heads. The pattern: greedy choice + heap = efficient implementation.
๐Ÿ’ก Two-Pointer Greedy Assign resources optimally by sorting both supply and demand, then matching from one end. "Assign cookies to children": sort children by greed factor, sort cookies by size, match smallest satisfying cookie to least greedy child. This greedy pairing works because using a bigger cookie than necessary wastes capacity that could satisfy a greedier child.

Common Greedy Patterns

Practice Problems

ProblemDifficultyPatternLink
Jump GameMediumFarthest reachableLC #55
Jump Game IIMediumBFS / greedy jumpsLC #45
Non-overlapping IntervalsMediumActivity selectionLC #435
Meeting Rooms IIMediumInterval + heapLC #253
Gas StationMediumCircular greedyLC #134
Task SchedulerMediumFrequency countingLC #621
Min Arrows to Burst BalloonsMediumInterval overlapLC #452
Partition LabelsMediumInterval + last occurrenceLC #763