CodeGround
DashboardProblemsInterviewsAssessmentsCourses
CodeGround
DashboardProblemsInterviewsAssessmentsCourses
The Technical Interview Course

0 of 170 lessons complete

  • The smallest thing, cheaply
  • Keeping only k candidates
  • Ordering by something you compute
  • Always take the most urgent thing
  • Merging many ordered sources
  • Two heaps facing each other
  • The interval problem that needed a heap
  • Retrieval: when is it a heap?

Heaps and Priority Queues

87 / 170

Heaps and Priority Queues/Lesson 7 of 8·About 53 min

The interval problem that needed a heap

Deferred from Unit 6 on purpose. Sort the queries, sweep the intervals, and keep the live ones in a heap.

Key idea

Why this waited

Unit 6 covered sorting intervals, merging them, and sweeping through events. It deliberately left one problem out: for each of many query points, report the size of the smallest interval covering that point.

The sweep is familiar. What was missing is a structure that can hand you the smallest live interval at any moment, and that is a heap.

Why it works

Answer the queries in a different order than asked

The queries arrive in arbitrary order and the answers must be returned in that order. Nothing says you have to compute them in that order.

Sort the queries ascending, sweep through them, and remember each answer against its original position. Restoring the original order at the end is bookkeeping. Being allowed to reorder the work is what makes the sweep possible at all, and this technique has a name worth knowing: answering queries offline.

The sweep

Sort the intervals by start. Walk the sorted queries, and for each one: add every interval that has started by now, discard from the top every interval that has already ended, then read the smallest remaining.

Below is what that looks like at a single query point, with the intervals added by hand instead of by a moving index.

1import heapq2 3intervals = [[1, 4], [2, 4], [3, 6], [4, 4]]4point = 35 6heap = []                                # (size, end), smallest size on top7for start, end in intervals:8    if start <= point:                   # has begun by now9        heapq.heappush(heap, (end - start + 1, end))10 11print("started by now:", sorted(heap))12while heap and heap[0][1] < point:13    heapq.heappop(heap)                  # already ended, and never comes back14print("smallest still covering:", heap[0][0])
Keying on size puts the answer on top; the end value is only there so expired entries can be recognized.

Why it works

Why discarding from the top is enough

A heap cannot remove an arbitrary element, and the expired intervals are not necessarily at the top. That sounds fatal and is not.

The only thing ever read is the top. So an expired interval buried in the middle causes no harm; it will be discarded when it eventually surfaces. Discarding from the top until the top is valid is enough to guarantee the answer is correct.

This is called lazy deletion, and it is the standard way to work around a heap's inability to remove from the middle. Each interval is pushed once and popped at most once, so the total cost stays bounded even though the heap may temporarily hold junk.

Tip

Both while loops only ever move forward

Queries are processed in ascending order, so the set of started intervals only grows and the set of expired ones only grows. Neither loop ever has to undo anything.

That monotonicity is what makes the whole thing linear in the number of pushes and pops, and it is why sorting the queries was the enabling step rather than a convenience.

Gotcha

Remember to restore the original order

Answers are computed in sorted-query order and must be returned in the caller's order. Writing into answers[index] rather than appending is what handles that.

Appending and returning would give correct values in the wrong positions, which passes any test whose queries happen to arrive sorted. Include an unsorted query list in your own testing.

Predict the output· 5 min

Predict the output: answering out of order and restoring it

The queries have to be answered in the order they were asked, but nothing says they have to be computed in that order. Type the two lines it prints.

Python
1queries = [5, 2, 9, 3]2 3order = sorted(range(len(queries)), key=lambda i: queries[i])4print("visit the queries in this positional order:", order)5 6answers = [None] * len(queries)7for position in order:8    answers[position] = queries[position] * 10      # stand-in for real work9 10print(answers)
Problem· 35 min

Minimum Interval To Include Each Query

Read the constraints first and let them tell you what complexity is expected. Derive the approach, implement it, run the tests, and submit when it passes.

PreviousTwo heaps facing each other
NextRetrieval: when is it a heap?

0 of 3 parts complete