Pattern 16 of 20

Intervals

Sort by start or end, then sweep: merge, insert and count overlaps.

When to use it

  • Inputs are ranges, meetings or bookings
  • Merging overlaps or finding free time
  • Counting how many ranges are active at once

The idea

Once intervals are sorted, overlaps only happen between neighbours, so one pass decides everything. Sorting by end time instead of start time is the trick behind maximising non-overlapping ranges.

Typical cost O(n log n) time for the sort, O(n) space for the output.

How it works

Interval problems become easy the moment the intervals are sorted. Sorted by start, overlapping intervals sit next to each other, so a single pass can merge them, count gaps, or insert a new one. Without sorting, every interval must be compared with every other.

Two intervals [a, b] and [c, d] overlap exactly when a <= d and c <= b (or with strict inequalities if touching endpoints do not count). Decide the touching rule up front; it is the most common source of wrong answers.

For 'remove the fewest intervals to make the rest disjoint' or 'schedule the most meetings', sort by end time instead. Taking the interval that finishes first leaves the most room for everything after it.

For 'how many overlap at once' (meeting rooms, maximum concurrent events), sweep over sorted events: +1 at each start and -1 at each end, or keep a min-heap of end times. The peak of the running count is the answer.

How to attack it

  1. 1Fix the overlap rule, including whether touching endpoints overlap.
  2. 2Sort by start for merging and inserting, by end for selecting the most disjoint.
  3. 3Keep a 'current' interval and extend or close it in one pass.
  4. 4Use a sweep or a heap of end times for maximum concurrency.
  5. 5Check empty input and a single interval.

Template

A skeleton to adapt, not a solution to paste. Write it from memory a few times.

import heapq


def merge(intervals):
    intervals.sort(key=lambda iv: iv[0])     # sort by start
    merged = []
    for start, end in intervals:
        if merged and start <= merged[-1][1]:  # overlaps the last one
            merged[-1][1] = max(merged[-1][1], end)
        else:
            merged.append([start, end])
    return merged


def max_disjoint(intervals):
    intervals.sort(key=lambda iv: iv[1])     # sort by end
    count, last_end = 0, float("-inf")
    for start, end in intervals:
        if start >= last_end:                # fits after the last chosen
            count += 1
            last_end = end
    return count


def rooms_needed(intervals):
    intervals.sort(key=lambda iv: iv[0])
    ends = []                                # min-heap of end times in use
    for start, end in intervals:
        if ends and ends[0] <= start:
            heapq.heappop(ends)              # reuse the room that freed up
        heapq.heappush(ends, end)
    return len(ends)

Variants

Merge overlapping
Sort by start and extend the last merged interval while the next one overlaps it.
Insert into a sorted list
Copy intervals that end before the new one, merge those that overlap, then copy the rest; O(n) with no sort.
Most non-overlapping
Sort by end and keep each interval that starts after the last kept end; removals are n minus that count.
Maximum concurrency
Sweep over start and end events, or keep a min-heap of end times; the peak count is the number of rooms.
Intersections of two lists
Two pointers over two sorted lists; advance the one that ends first.

Pitfalls

  • Not deciding whether [1, 2] and [2, 3] overlap, then mixing < and <= across the code.
  • Forgetting to sort, or sorting by the wrong endpoint for the question.
  • Replacing the end with the new end instead of the maximum of the two when merging.
  • Mutating the caller's input when sorting in place if the problem forbids it.
  • In a sweep, processing starts before ends at the same time, which overcounts rooms.

What it costs

OperationCost
SortO(n log n)
Merge or select in one passO(n) after sorting
Insert into an already sorted listO(n)
Rooms with a heap or a sweepO(n log n)

25 Intervals problems

Easiest first. Problems marked Free can be solved without Pro.

  1. 1Clash-Free CalendarFreeEasy
  2. 2Total Alarm TimeFreeEasy
  3. 3Days Off the CalendarFreeEasy
  4. 4Group Consecutive BadgesEasy
  5. 5Painted Fence LengthEasy
  6. 6Longest Free GapEasy
  7. 7Shift Coverage CountsEasy
  8. 8Consolidate RangesMedium
  9. 9Slot a New Range InMedium
  10. 10Fewest Meeting RoomsMedium
  11. 11Cancel the FewestMedium
  12. 12Fewest Inspection PointsMedium
  13. 13Carve Out a WindowMedium
  14. 14Count Essential RangesMedium
  15. 15Peak Server LoadMedium
  16. 16Free for EveryoneMedium
  17. 17Next Available SlotMedium
  18. 18Self-Contained ChunksMedium
  19. 19Cover the Whole ShiftMedium
  20. 20One Event Per DayMedium
  21. 21Most Profitable GigsHard
  22. 22Busiest StudioHard
  23. 23Tightest Covering RangeHard
  24. 24Stage OutlineHard
  25. 25Two Checkpoints EachHard

Solve them in the browser, in Python or JavaScript.