Chapter 22: Intervals, Events and Sweep-Line Algorithms
22.03Intermediate5 topics17 min4 interactive animations

Difference-array event sweep

The event-sweep technique: turning intervals into start and end events, ordering ties correctly, tracking the active count, finding maximum concurrency and applying range updates with a difference array.

Learning goals

  • Convert intervals to +1/-1 events and sweep them in sorted order
  • Order simultaneous events so closed and half-open conventions give the right answer
  • Compute maximum concurrency and the time it first occurs
  • Apply many range updates with a difference array and one prefix sum

Prerequisites

Interval modelingArrays and prefix sums

Many questions only need to know how many intervals are active at each moment. Sorting the endpoints answers them in O(n log n) without comparing intervals against each other.

22.03.1

Start/end events

Replace every interval [s, e) by two **events**: at time s the active count goes up by 1, at time e it goes down by 1. Sort all events by time and process them in order; between events nothing changes, so only event times matter, whatever the distance between them.

The “Meeting rooms” preset of the animation turns four meetings into eight events, shows the active count after each, and tracks the maximum. This is why the technique is called a **sweep line**: an imaginary vertical line moves across the time axis and stops only at events.

Java
1// events: {time, delta}; delta = +1 for a start, -1 for an end
2static int[][] toEvents(int[][] intervals) {
3 int[][] events = new int[intervals.length * 2][];
4 int k = 0;
5 for (int[] iv : intervals) {
6 events[k++] = new int[]{iv[0], +1};
7 events[k++] = new int[]{iv[1], -1};
8 }
9 return events;
10}

Start/end events: active count and maximum concurrency

Interactive · edit the input

Your input

Try

intervals

[0, 30]
[5, 10]
[15, 20]
[10, 15]
0123456789101112131415161718192021222324252627282930

events in sweep order

+0
+5
−10
+10
−15
+15
−20
−30

active count after each event

none
Setup

Turn every interval into two events, +1 at its start and −1 at its end, sort them, and sweep once keeping a running count. At equal coordinates ends come first, because [s, e) does not contain e: back-to-back intervals do not overlap.

Step 1 / 10
Java
1static int maxConcurrent(int[][] iv, boolean closed) {
2 List<int[]> events = new ArrayList<>(); // {coordinate, +1 start / -1 end}
3 for (int[] x : iv) { events.add(new int[]{x[0], +1}); events.add(new int[]{x[1], -1}); }
4 events.sort((a, b) -> a[0] != b[0] ? Integer.compare(a[0], b[0])
5 : closed ? Integer.compare(b[1], a[1]) : Integer.compare(a[1], b[1])); // tie: closed -> start first; half-open -> end first
6 int active = 0, best = 0;
7 for (int[] e : events) {
8 active += e[1]; // a start opens one, an end closes one
9 best = Math.max(best, active);
10 }
11 return best;
12}

Live state

intervals

4

events

8

No condition evaluated on this step
Execution trace (dry run table)Show 10 steps
StepLineStateWhat happens
1L2intervals=4 events=8Turn every interval into two events, +1 at its start and −1 at its end, sort them, and sweep once keeping a running count. At equal coordinates ends come first, because [s, e) does not contain e: back-to-back intervals do not overlap.
2L8x=0 event=start active=1 best=1At 0: an interval starts, so 1 is active. New maximum: 1.
3L8x=5 event=start active=2 best=2At 5: an interval starts, so 2 are active. New maximum: 2.
4L8x=10 event=end active=1 best=2At 10: an interval ends, so 1 is active.
5L8x=10 event=start active=2 best=2At 10: an interval starts, so 2 are active.
6L8x=15 event=end active=1 best=2At 15: an interval ends, so 1 is active.
7L8x=15 event=start active=2 best=2At 15: an interval starts, so 2 are active.
8L8x=20 event=end active=1 best=2At 20: an interval ends, so 1 is active.
9L8x=30 event=end active=0 best=2At 30: an interval ends, so 0 are active.
10L11return=2Maximum concurrency 2: that many rooms (or platforms, or CPUs) are needed. Cost O(n log n) for the sort, O(n) for the sweep.
22.03.2

Event ordering

When a start and an end share the same time, the processing order decides the answer. For half-open intervals [s, e) a meeting that ends at 3 has left before one that starts at 3 arrives, so **ends are processed first** at equal times. For closed intervals the two meetings share the instant 3, so **starts come first**.

The animation shows both: with the data 1-3, 3-5, 5-7, the “Back-to-back: half-open needs 1” preset reports maximum concurrency 1, and “Back-to-back: closed needs 2” reports 2. A single comparator decides it: sort by time, then by delta with -1 before +1 for half-open.

Java
1// half-open: at equal times the end (-1) sorts before the start (+1)
2static void sortEvents(int[][] events) {
3 Arrays.sort(events, (x, y) -> x[0] != y[0] ? Integer.compare(x[0], y[0]) : Integer.compare(x[1], y[1]));
4}
5// for closed intervals reverse the tie-break: Integer.compare(y[1], x[1])

Start/end events: active count and maximum concurrency

Interactive · edit the input

Your input

Try

intervals

[0, 30]
[5, 10]
[15, 20]
[10, 15]
0123456789101112131415161718192021222324252627282930

events in sweep order

+0
+5
−10
+10
−15
+15
−20
−30

active count after each event

none
Setup

Turn every interval into two events, +1 at its start and −1 at its end, sort them, and sweep once keeping a running count. At equal coordinates ends come first, because [s, e) does not contain e: back-to-back intervals do not overlap.

Step 1 / 10
Java
1static int maxConcurrent(int[][] iv, boolean closed) {
2 List<int[]> events = new ArrayList<>(); // {coordinate, +1 start / -1 end}
3 for (int[] x : iv) { events.add(new int[]{x[0], +1}); events.add(new int[]{x[1], -1}); }
4 events.sort((a, b) -> a[0] != b[0] ? Integer.compare(a[0], b[0])
5 : closed ? Integer.compare(b[1], a[1]) : Integer.compare(a[1], b[1])); // tie: closed -> start first; half-open -> end first
6 int active = 0, best = 0;
7 for (int[] e : events) {
8 active += e[1]; // a start opens one, an end closes one
9 best = Math.max(best, active);
10 }
11 return best;
12}

Live state

intervals

4

events

8

No condition evaluated on this step
Execution trace (dry run table)Show 10 steps
StepLineStateWhat happens
1L2intervals=4 events=8Turn every interval into two events, +1 at its start and −1 at its end, sort them, and sweep once keeping a running count. At equal coordinates ends come first, because [s, e) does not contain e: back-to-back intervals do not overlap.
2L8x=0 event=start active=1 best=1At 0: an interval starts, so 1 is active. New maximum: 1.
3L8x=5 event=start active=2 best=2At 5: an interval starts, so 2 are active. New maximum: 2.
4L8x=10 event=end active=1 best=2At 10: an interval ends, so 1 is active.
5L8x=10 event=start active=2 best=2At 10: an interval starts, so 2 are active.
6L8x=15 event=end active=1 best=2At 15: an interval ends, so 1 is active.
7L8x=15 event=start active=2 best=2At 15: an interval starts, so 2 are active.
8L8x=20 event=end active=1 best=2At 20: an interval ends, so 1 is active.
9L8x=30 event=end active=0 best=2At 30: an interval ends, so 0 are active.
10L11return=2Maximum concurrency 2: that many rooms (or platforms, or CPUs) are needed. Cost O(n log n) for the sort, O(n) for the sweep.
22.03.3

Active count

The **active count** after processing an event is the running sum of the deltas, which is the number of intervals containing the current time. A difference array gives the same sweep for small integer coordinates without sorting: add 1 at the start index, subtract 1 at the end index, and a prefix sum over the array gives the count at every position.

For half-open intervals [s, e) the two array writes are diff[s]++ and diff[e]--. Positions are bounded by the largest coordinate, so this suits days of a year or minutes of a day; for huge coordinates sort events or compress them, as the last group in this chapter shows.

Java
1// counts[t] = number of half-open intervals [s, e) containing time t, for 0 <= t < maxTime
2static int[] activeCounts(int[][] intervals, int maxTime) {
3 int[] diff = new int[maxTime + 1];
4 for (int[] iv : intervals) { diff[iv[0]]++; diff[iv[1]]--; }
5 int[] counts = new int[maxTime];
6 int running = 0;
7 for (int t = 0; t < maxTime; t++) { running += diff[t]; counts[t] = running; }
8 return counts;
9}

Start/end events: active count and maximum concurrency

Interactive · edit the input

Your input

Try

intervals

[0, 30]
[5, 10]
[15, 20]
[10, 15]
0123456789101112131415161718192021222324252627282930

events in sweep order

+0
+5
−10
+10
−15
+15
−20
−30

active count after each event

none
Setup

Turn every interval into two events, +1 at its start and −1 at its end, sort them, and sweep once keeping a running count. At equal coordinates ends come first, because [s, e) does not contain e: back-to-back intervals do not overlap.

Step 1 / 10
Java
1static int maxConcurrent(int[][] iv, boolean closed) {
2 List<int[]> events = new ArrayList<>(); // {coordinate, +1 start / -1 end}
3 for (int[] x : iv) { events.add(new int[]{x[0], +1}); events.add(new int[]{x[1], -1}); }
4 events.sort((a, b) -> a[0] != b[0] ? Integer.compare(a[0], b[0])
5 : closed ? Integer.compare(b[1], a[1]) : Integer.compare(a[1], b[1])); // tie: closed -> start first; half-open -> end first
6 int active = 0, best = 0;
7 for (int[] e : events) {
8 active += e[1]; // a start opens one, an end closes one
9 best = Math.max(best, active);
10 }
11 return best;
12}

Live state

intervals

4

events

8

No condition evaluated on this step
Execution trace (dry run table)Show 10 steps
StepLineStateWhat happens
1L2intervals=4 events=8Turn every interval into two events, +1 at its start and −1 at its end, sort them, and sweep once keeping a running count. At equal coordinates ends come first, because [s, e) does not contain e: back-to-back intervals do not overlap.
2L8x=0 event=start active=1 best=1At 0: an interval starts, so 1 is active. New maximum: 1.
3L8x=5 event=start active=2 best=2At 5: an interval starts, so 2 are active. New maximum: 2.
4L8x=10 event=end active=1 best=2At 10: an interval ends, so 1 is active.
5L8x=10 event=start active=2 best=2At 10: an interval starts, so 2 are active.
6L8x=15 event=end active=1 best=2At 15: an interval ends, so 1 is active.
7L8x=15 event=start active=2 best=2At 15: an interval starts, so 2 are active.
8L8x=20 event=end active=1 best=2At 20: an interval ends, so 1 is active.
9L8x=30 event=end active=0 best=2At 30: an interval ends, so 0 are active.
10L11return=2Maximum concurrency 2: that many rooms (or platforms, or CPUs) are needed. Cost O(n log n) for the sort, O(n) for the sweep.
22.03.4

Maximum concurrency

**Maximum concurrency** is the largest active count over the sweep, and it answers 'how many rooms, platforms, servers or CPUs are needed'. A TreeMap from time to net delta handles repeated times and arbitrary coordinates, and iterating it in key order is the sweep. Because all events at one time are combined into a single net delta, ties cannot be mis-ordered: an end and a start at the same time cancel before the count is compared to the maximum.

On the “All at once” preset, 4 nested intervals all active together give concurrency 4, and on the classic meeting-rooms data the answer is 3. The cost is O(n log n) for the map operations.

Java
1static int maxConcurrency(int[][] intervals) { // half-open [start, end)
2 TreeMap<Integer, Integer> delta = new TreeMap<>();
3 for (int[] iv : intervals) {
4 delta.merge(iv[0], +1, Integer::sum);
5 delta.merge(iv[1], -1, Integer::sum);
6 }
7 int active = 0, best = 0;
8 for (int d : delta.values()) { // keys in increasing time order
9 active += d;
10 best = Math.max(best, active);
11 }
12 return best;
13}

Best

O(n log n)

Average

O(n log n)

Worst

O(n log n)

Space

O(n)

Minimum meeting rooms (sweep sorted events)

Interactive · edit the input

Your input

Try
t=0

meetings

[0, 30]
[5, 10]
[10, 25]
[15, 20]
[22, 28]
0123456789101112131415161718192021222324252627282930

starts (sorted)

0
5
10
15
22

ends (sorted)

10
20
25
28
30
Setup

Only the times matter, not which meeting is which. Sort starts and ends separately and sweep left to right.

Step 1 / 9
Java
1public static int minRooms(int[][] meetings) { // [start, end), end is exclusive
2 int n = meetings.length;
3 int[] starts = new int[n], ends = new int[n];
4 for (int i = 0; i < n; i++) { starts[i] = meetings[i][0]; ends[i] = meetings[i][1]; }
5 Arrays.sort(starts);
6 Arrays.sort(ends);
7 int rooms = 0, best = 0, e = 0;
8 for (int s = 0; s < n; s++) {
9 while (ends[e] <= starts[s]) { e++; rooms--; } // meetings that ended free a room
10 rooms++;
11 best = Math.max(best, rooms);
12 }
13 return best;
14}

Live state

0 events

rooms

0

best

0

No condition evaluated on this step
Execution trace (dry run table)Show 9 steps
StepLineStateWhat happens
1L5rooms=0 best=0Only the times matter, not which meeting is which. Sort starts and ends separately and sweep left to right.
2L11time=0 rooms=1 best=1A meeting starts at 0: 1 room in use. Peak so far 1.
3L11time=5 rooms=2 best=2A meeting starts at 5: 2 rooms in use. Peak so far 2.
4L9time=10 rooms=1 best=2A meeting ended at 10 ≤ 10: its room is free again.
5L11time=10 rooms=2 best=2A meeting starts at 10: 2 rooms in use. Peak so far 2.
6L11time=15 rooms=3 best=3A meeting starts at 15: 3 rooms in use. Peak so far 3.
7L9time=22 rooms=2 best=3A meeting ended at 20 ≤ 22: its room is free again.
8L11time=22 rooms=3 best=3A meeting starts at 22: 3 rooms in use. Peak so far 3.
9L13return=33 rooms needed: the maximum number of meetings overlapping at any moment. O(n log n).
22.03.5

Range coverage

The difference array also solves **many range updates, one read**: 'add v to every element from l to r' repeated m times and then read the whole array. Record diff[l] += v and diff[r + 1] -= v for each update, then take one prefix sum. Each update is O(1), and the final pass is O(n), so m updates cost O(m + n) instead of O(m · n).

For example, on an array of 5 zeros, adding 2 to indexes 1 to 3 and 3 to indexes 2 to 4 sets diff to [0, 2, 3, 0, -2, -3], whose prefix sums are [0, 2, 5, 5, 3, 0], so the first five entries are 0, 2, 5, 5, 3.

A difference array supports updates in bulk but not reads between updates. If you must interleave range updates and queries, use a Fenwick or segment tree, introduced later in the course.

Index012345
diff0230-2-3
Prefix sum025530
Java
1// updates[i] = {l, r, v}: add v to every index in [l, r] (inclusive)
2static int[] applyRangeUpdates(int n, int[][] updates) {
3 int[] diff = new int[n + 1];
4 for (int[] u : updates) {
5 diff[u[0]] += u[2];
6 diff[u[1] + 1] -= u[2];
7 }
8 int[] result = new int[n];
9 int running = 0;
10 for (int i = 0; i < n; i++) { running += diff[i]; result[i] = running; }
11 return result;
12}

Best

O(n + m)

Average

O(n + m)

Worst

O(n + m)

Space

O(n)

Practice ladder

Now try it yourself

Easy

  • Car Pooling
  • Corporate Flight Bookings

Medium

  • Meeting Rooms II
  • My Calendar III
  • Describe the Painting

Advanced

  • The Skyline Problem
  • Number of Flowers in Full Bloom

Key takeaways

  • An interval is two events, +1 at its start and -1 at its end; sort them and keep a running total.
  • At equal times process ends before starts for half-open intervals, starts before ends for closed ones.
  • A difference array turns many range updates into two writes each plus one prefix sum.
  • Maximum concurrency over the sweep is the number of rooms, platforms or servers required.