All topics
27

Bumping a whole range without touching every slot

Tier 1Free

Where it runs

1. The problem

You run a booking calendar. At the start every day has zero guests. One booking takes days 3 to 9 for 4 guests, so you add 4 to each of those seven days. Another takes days 2 to 20 for 2 guests, so you add 2 to nineteen days. And so on, for thousands of bookings.

The first idea: for each booking, walk over its days and add the guests. One booking for 300 days is 300 additions. Ten thousand bookings like that make millions of writes, and you haven't even checked for overbooking yet.

You read the final number for each day only once, at the end. Until then you just stack range additions on top of each other. There is a way to record each such addition with two marks, then turn all the marks into the final numbers in one pass.

2. In plain words

Take 5 days, numbered 0 to 4, and the booking "+2 guests on days 1 to 3". Instead of three additions you leave two marks. On day 1 you write +2: from here on there are 2 more guests. On day 4, right after the booking ends, you write -2: from here on those guests are gone.

You can leave as many pairs of marks as you like, for any bookings. At the very end you walk through the days once from the start and keep the total of all the marks you have met. Day 0: 0. Days 1, 2, 3: 2 each. Day 4: 0 again. Two marks per booking and one pass at the end.

3. The idea

Make a helper array diff, one cell longer than the data, filled with zeros. To add val over the range [l, r], leave two marks: diff[l] += val and diff[r+1] -= val. The +val mark turns the addition on from l. The -val mark turns it off right after r.

Nothing else is needed for one update. Two writes, however wide the range is.

To get the real values, walk from left to right and keep a running total: running += diff[i], then result[i] = running. The running total carries each +val forward from the place where it was turned on and loses it at r+1. So exactly the days from l to r get the addition.

Subtract at r+1, not at r. The addition has to last through day r, so it is turned off one cell later. If you turn it off at r, the last day of every range misses its addition.

diff (mark +2 at 1, -2 at 4): 0 +2 0 0 -2 0 sweep → result: 0 2 2 2 0

Trace it on n = 5 with the updates [[1,3,2], [2,4,1]].

  • Update [1,3,2]: diff[1] += 2, diff[4] -= 2.
  • Update [2,4,1]: diff[2] += 1, diff[5] -= 1.
  • Now diff = [0, 2, 1, 0, -2, -1].
  • The running total: 0, then 0 + 2 = 2, then 2 + 1 = 3, then 3 + 0 = 3, then 3 - 2 = 1. So result = [0, 2, 3, 3, 1].

Check it by hand: +2 on days 1 to 3 and +1 on days 2 to 4 give exactly this.

4. What it costs

Each update is two writes: O(1). For u updates that is O(u). The final pass visits each cell once: O(n). The total is O(n + u). Memory is O(n) for the diff array.

Adding to every element by hand costs O(n) per update and O(n·u) in total. The marks at the two ends remove that factor.

There is one condition: the method works when all the writes come first and the reading comes after. If reads and writes take turns (write, read, write, read), a plain difference array can't keep up. That needs a Fenwick tree or a segment tree, which come much later in the course.

5. Where this shows up

Occupancy of seats and days in a calendar with capacity checks, and finding overbooked flights and hotels. Applying many rules such as "add N over this range of IP addresses" or "raise prices in this price range", after which the result is read once. A heatmap that gets thousands of ranges painted onto it and is then drawn once. Anywhere a batch of range additions piles up first and the totals are looked at later.

Range updates with a difference array
applyRangeUpdates(6, [[1, 3, 2], [2, 5, 3], [0, 1, 1]])
diff (the change at each index)
0
0
0
0
0
0
0
result
running = 0

Apply 3 range updates to 6 zeroes. diff is one slot longer, so r + 1 always has a place.

Speed race
40 range additions on the same 40 cells

Bars: the 40 cells. The difference array writes its marks to a side array, so its bars only move in the final pass. There are as many updates as cells, here and in the size table.

One operation is one write. Every frame, each lane does the same number of operations, so the slow one falls behind.

Add to every cell O(n·u)
0 operations
Difference array O(n + u)
0 operations

 

And with more numbers

Operations for n numbers, and roughly how long they take at 100 million simple operations a second. An estimate from the same count, not a measurement.

n = 1,000
Add to every cell334.3K · 3.3 ms
Difference array3K · under 1 ms
n = 100,000
Add to every cell3.3B · 33 s
Difference array300K · 3 ms
n = 10,000,000
Add to every cell33.3T · 3.9 days
Difference array30M · 300 ms

Recognise it

Difference array

Say these words and it is this pattern

  • many range updates first, then read the final array once
  • add a value to every element in a range, repeated many times
  • apply k range increments before answering any query
  • booking/interval counters over an array

Looks similar, isn't

  • Prefix sums

    Prefix sums read many ranges from data that never changes. This is the mirror: many writes, one read.

  • Sweep line (event counting)

    A sweep sorts events by coordinate, because the coordinates are huge or sparse. Here they index an array directly.

  • Fenwick tree / segment tree

    A Fenwick tree answers queries in between updates. A difference array only answers after the last one.

  • Sliding window (fixed size)

    A window walks one span of fixed width. The ranges here differ in width and overlap freely.

n up to 1e6 with m range updates gives O(n + m). Each update is two writes, and the rebuild is one pass.

On LeetCode

Problems where many updates add a value to a whole range and you read the result once at the end, such as bookings, trips and letter shifts. Mark the two ends of each range and add up the marks in one pass.

Your task (Tier 1)

Write the function applyRangeUpdates(n, updates).

Start with a list of n zeros. Each update adds a value to a range of positions. Apply all updates and return the final list. The method: mark only the two ends of each range, then add up the marks in one pass. That keeps even many updates cheap.

  • Input: n, the length of the list, and updates, a list of updates. Each update is [l, r, val]: add val to every position from l to r, with r included. Example: n = 5, updates [[1, 3, 2], [2, 4, 1]].
  • Return: the final list of n numbers.

Steps:

  1. Make a helper list diff of n + 1 zeros. The extra cell at the end takes the r + 1 mark when a range reaches the last position.
  2. For each update [l, r, val], add val at position l in diff and subtract val at position r + 1.
  3. Go through diff from left to right and keep a running total. Write it into each position of the result list.

Done when all tests pass:

  • n = 5, [[1,3,2],[2,4,1]] -> [0, 2, 3, 3, 1] (two ranges overlap)
  • n = 3, [[0,2,5]] -> [5, 5, 5] (the range covers the whole list)
  • n = 4, [[2,2,7]] -> [0, 0, 7, 0] (one position)
  • n = 4, [[0,1,1],[1,3,1]] -> [1, 2, 1, 1] (both ranges include position 1)
  • n = 3, [] -> [0, 0, 0] (no updates, all zeros)
  • n = 1, [[0,0,9]] -> [9] (one position)

Practice

applyRangeUpdates

applyRangeUpdates(n, updates) -> number[]

export function applyRangeUpdates(n, updates) {
  // Your code here. The task is described in the box above the editor.
}

Running as a guest

The tests run right here, no account needed. Your code is kept in this browser only. A free account keeps it on every device, marks the step done and shows where you are in the course.

Hint ladder

Stuck? Reveal one rung at a time, but try it yourself first.

    We use necessary cookies to run the site (sign-in and language). If you accept, we also load Google Analytics to see which pages are used, and Google reCAPTCHA to keep spam off the contact and bug-report forms. Privacy policy