Sweep and Ranges
What is this?
A bus route has forty stops and a hundred bookings, each covering a stretch of the journey. To check the bus never exceeds capacity you could add every booking's passengers to every stop it covers — thousands of updates. Or you could note, at each stop, only the change: passengers boarding, passengers leaving. Walk the route once adding up the changes, and the running total at every stop is the load.
That is the difference-array idea, and it is the same instinct as grouping consecutive numbers into ranges: the interesting information lives at the boundaries.
💡 Fun fact: the difference array is the discrete derivative, and the prefix-sum pass that reconstructs the timeline is the discrete integral. Recording
+vand−vat two boundaries is exactly differentiating a step function, and integrating it back recovers the original. That is whykrange updates over a timeline of lengthncostO(k + n)rather thanO(k · n)— you never touch the interior of a range, only its two edges.
🔓 The 3 problems in this chapter are free. Sign in with Google or Microsoft to start solving.
The one-line idea: never touch the inside of a range. Record what changes at its two edges, make one ordered pass, and let the accumulation reconstruct everything in between.
1. The difference array
delta = [0] * (max_position + 1)
for passengers, start, end in trips:
delta[start] += passengers
delta[end] -= passengers # end is EXCLUSIVE — they get off here
load = 0
for stop in range(len(delta)):
load += delta[stop]
if load > capacity:
return False
return True
The exclusive end is the whole correctness argument. A passenger travelling [2, 5) is aboard at stops 2, 3 and 4 but not at 5 — so writing −v at 5 rather than 6 is what stops back-to-back trips being double-counted. Getting this wrong produces answers that are right for isolated ranges and wrong the moment two ranges touch.
2. Run grouping
ranges, i, n = [], 0, len(nums)
while i < n:
start = i
while i + 1 < n and nums[i + 1] == nums[i] + 1:
i += 1 # extend while consecutive
ranges.append(str(start_val) if start == i else f"{nums[start]}->{nums[i]}")
i += 1
Two details cause most bugs here. The single-element case formats differently ("4", not "4->4"), and the final run must be emitted after the loop ends — there is no "next value" to trigger its closure.
3. A 30-second worked example (car pooling)
Trips (2 passengers, 1→5), (3 passengers, 3→7), capacity 4:
The two trips overlap on stops 3 and 4, and the running total exposes that at stop 3 without ever enumerating which trips are aboard. With capacity 5 the same sweep would return True.
4. Where you'll actually meet this
- Capacity checks. Vehicle occupancy, room bookings and connection pools — anything where claims overlap in time.
- Monitoring and alerting. Counting concurrent requests over a window without storing each one.
- Genomics. Coverage depth along a reference sequence is a difference array over read alignments.
- Billing and metering. Aggregating overlapping usage windows into a per-interval total.
- Data compression and display. Collapsing consecutive identifiers into ranges —
1-5, 8, 11-14— in page selectors and log summaries.
5. Problems in this chapter
▶ Summary Ranges
Compress a sorted array into range strings. One pass holding the start of the current run, closing it when the chain breaks, and flushing the final run afterwards.
Pattern: run grouping. Target: O(n) time, O(1) extra space.
▶ Car Pooling
Decide whether a vehicle's capacity is ever exceeded. Difference array over the route, then a prefix-sum sweep checking the running load.
Pattern: difference array + prefix sum. Target: O(k + n) time, O(n) space.
6. Common pitfalls 🚫
- Using an inclusive end.
−vmust land atend, notend + 1, or touching trips double-count. - Forgetting the last run. Nothing triggers its closure inside the loop, so it must be emitted afterwards.
- Formatting single elements as a range.
"4", not"4->4". - Sizing the delta array too small. It needs one slot past the maximum coordinate.
- Sorting trips before sweeping. Unnecessary when coordinates are bounded — index directly. Sorting only helps when coordinates are sparse or huge.
- Checking capacity after the sweep. The limit must be tested at every stop, since the peak may occur mid-route.
- Assuming the input is sorted in Summary Ranges. It is stated as sorted; if it were not, that assumption would silently produce wrong ranges.
- Missing Ranges — report the gaps between a sorted array and a bounded interval. The complement of run-grouping: walk the values tracking the next expected number and emit a range wherever the sequence skips.
7. Key takeaways
- Record deltas, not values. Two point writes replace a whole range update.
- The end is exclusive. That single convention is what keeps adjacent ranges honest.
- One prefix-sum pass reconstructs the timeline, and the running total answers the question at every point.
- Close the final run after the loop.
- Bounded coordinates mean direct indexing; sparse or huge ones mean sorting the events instead.
- This is the counting cousin of merging. Merging asks which intervals overlap; sweeping asks how many.
- Why interviewers like it: the naive per-position update is the obvious answer and is quadratic. Reaching for boundaries instead is a small, transferable insight they can test in ten minutes.
Order: Summary Ranges → Car Pooling → Missing Ranges.
Missing Ranges
Core idea: You're given a sorted, distinct array
numsand an inclusive window[lower, upper]. You must report every sub-range of[lower, upper]that the array does not cover. The trap is the boundaries: a gap can hide before the first element (betweenlowerandnums[0]), between two consecutive elements, or after the last element (betweennums[-1]andupper). The clean trick is to walk with aprevpointer that starts conceptually atlower - 1, then iterate over each number innumsplus one sentinelupper + 1appended at the end. For eachcur, the open interval strictly betweenprevandcur— that is[prev + 1, cur - 1]— is missing if and only ifcur - prev >= 2. Record it, then setprev = curand move on. Startingprevatlower - 1makes the very first gap fall out of the same formula; theupper + 1sentinel makes the trailing gap fall out too. One pass, no special-casing for the ends → O(n). The whole problem is the boundary bookkeeping.
The problem, rephrased
You run a fleet of servers that each emit a strictly increasing sequence number on every event. Over a billing window you expect numbers from lower to upper inclusive. You collect the ones that actually arrived — already sorted and de-duplicated by the ingest pipeline — and now you must produce a tidy report of exactly which stretches are missing, so on-call can tell "we dropped 12–14 and 20–20" at a glance. A single dropped number should read as a one-element range; a long outage should collapse into one range, not a dump of every individual id.
Formally: given a sorted array of distinct integers nums and two integers lower and upper (with lower <= upper), return the smallest sorted list of ranges [start, end] (each inclusive) that exactly covers every integer in [lower, upper] not present in nums. Adjacent missing numbers must be merged into a single range.
This is LeetCode 163 — Missing Ranges.
| Input | Means | Output |
|---|---|---|
nums=[0,1,3,50,75], lower=0, upper=99 |
gaps after 1, 3, 50, 75 | [[2,2],[4,49],[51,74],[76,99]] |
nums=[-1], lower=-1, upper=-1 |
the only expected value is present | [] |
nums=[], lower=1, upper=8 |
nothing arrived | [[1,8]] |
Every missing range is "the empty space between two consecutive present values" — where the two boundary endpoints lower and upper count as honorary present values just outside the window.
Sign in to continue reading
The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.
Sign in to read the full lesson