Sort the intervals by start and process queries in sorted order. A min-heap keyed by interval size lazily admits intervals that could cover the current query and evicts those that already ended — the heap top is always the smallest valid answer.
You have a list of intervals [[start, end], …] and a list of integer queries. For each query q, find the size (i.e. end − start + 1) of the smallest interval that contains q. An interval [a, b] contains q when a ≤ q ≤ b. If no interval covers q, answer -1 for that query.
Concrete example: intervals = [[1,4],[2,4],[3,6],[4,4]], queries = [2,3,4,5] → answer [3,3,1,4].
q=2: covered by [1,4] (size 4), [2,4] (size 3). Smallest size = 3.q=3: covered by [1,4], [2,4], [3,6]. Smallest = 3.q=4: also covered by [4,4] (size 1). Smallest = 1.q=5: only [3,6] (size 4). Answer = 4.[value, originalIndex] and sort by value. Answers go back into result[originalIndex] at the end.q:start ≤ q onto a min-heap keyed by size = end − start + 1. Store [size, end] — we need end for eviction.end < q: those intervals ended before qso they can't cover it.end. If you key by end, the top will be the interval that expires soonest — not the smallest one. You must push [size, end] so the min-heap orders by size (what the problem asks) while still letting you check end for expiry. Many solutions get this backwards on first attempt.Every interval is pushed once and popped at most once — the heap never grows beyond n entries. The eviction loop looks like it could be slow but across all queries each entry is popped at most once: amortised O(1) per eviction, O(n log n) total. Space: O(n) for the heap + O(q) for the answer array.
1function minInterval(intervals: number[][], queries: number[]): number[] {2 // Sort intervals by start value so we can add them lazily as queries grow3▶ intervals.sort((a, b) => a[0] - b[0]);45 // Process queries in ascending order, then restore original positions6▶ const indexedQueries = queries.map((q, i) => [q, i] as [number, number]);7▶ indexedQueries.sort((a, b) => a[0] - b[0]);89▶ const result: number[] = new Array(queries.length).fill(-1);1011 // Min-heap keyed by interval size: [size, end]12▶ const heap: [number, number][] = [];13▶ let ivIdx = 0;1415 const heapPush = (entry: [number, number]) => {16 heap.push(entry);17 let i = heap.length - 1;18 while (i > 0) {19 const p = Math.floor((i - 1) / 2);20 if (heap[p][0] <= heap[i][0]) break;21 [heap[p], heap[i]] = [heap[i], heap[p]];22 i = p;23 }24 };2526 const heapPop = (): [number, number] => {27 const top = heap[0];28 const last = heap.pop()!;29 if (heap.length > 0) {30 heap[0] = last;31 let i = 0;32 while (true) {33 let s = i;34 const l = 2 * i + 1, r = 2 * i + 2;35 if (l < heap.length && heap[l][0] < heap[s][0]) s = l;36 if (r < heap.length && heap[r][0] < heap[s][0]) s = r;37 if (s === i) break;38 [heap[s], heap[i]] = [heap[i], heap[s]];39 i = s;40 }41 }42 return top;43 };4445 for (const [q, origIdx] of indexedQueries) {46 // Add every interval whose start <= q (it could cover q)47 while (ivIdx < intervals.length && intervals[ivIdx][0] <= q) {48 const [start, end] = intervals[ivIdx];49 heapPush([end - start + 1, end]); // key = size, payload = end50 ivIdx++;51 }5253 // Evict intervals that ended before q (they can't cover q)54 while (heap.length > 0 && heap[0][1] < q) {55 heapPop();56 }5758 // Heap top = smallest still-valid interval covering q59 if (heap.length > 0) {60 result[origIdx] = heap[0][0];61 }62 }6364 return result;65}
function minInterval(intervals: number[][], queries: number[]): number[] {
// Sort intervals by start value so we can add them lazily as queries grow
intervals.sort((a, b) => a[0] - b[0]);
// Process queries in ascending order, then restore original positions
const indexedQueries = queries.map((q, i) => [q, i] as [number, number]);
indexedQueries.sort((a, b) => a[0] - b[0]);
const result: number[] = new Array(queries.length).fill(-1);
// Min-heap keyed by interval size: [size, end]
const heap: [number, number][] = [];
let ivIdx = 0;
const heapPush = (entry: [number, number]) => {
heap.push(entry);
let i = heap.length - 1;
while (i > 0) {
const p = Math.floor((i - 1) / 2);
if (heap[p][0] <= heap[i][0]) break;
[heap[p], heap[i]] = [heap[i], heap[p]];
i = p;
}
};
const heapPop = (): [number, number] => {
const top = heap[0];
const last = heap.pop()!;
if (heap.length > 0) {
heap[0] = last;
let i = 0;
while (true) {
let s = i;
const l = 2 * i + 1, r = 2 * i + 2;
if (l < heap.length && heap[l][0] < heap[s][0]) s = l;
if (r < heap.length && heap[r][0] < heap[s][0]) s = r;
if (s === i) break;
[heap[s], heap[i]] = [heap[i], heap[s]];
i = s;
}
}
return top;
};
for (const [q, origIdx] of indexedQueries) {
// Add every interval whose start <= q (it could cover q)
while (ivIdx < intervals.length && intervals[ivIdx][0] <= q) {
const [start, end] = intervals[ivIdx];
heapPush([end - start + 1, end]); // key = size, payload = end
ivIdx++;
}
// Evict intervals that ended before q (they can't cover q)
while (heap.length > 0 && heap[0][1] < q) {
heapPop();
}
// Heap top = smallest still-valid interval covering q
if (heap.length > 0) {
result[origIdx] = heap[0][0];
}
}
return result;
}ivIdx can sweep forward as queries increase — no need to revisit earlier intervals.heapPush and heapPopordered by the first tuple element (size). In an interview you can name "min-heap keyed by size" and code just the usage.[size, end]. This is O(log n) per push and happens at most n times total.q, so it can't contain it. Amortised O(1): each entry evicted at most once across all queries.q and (b) ends ≥ q. Its heap[0][0] is the size. Store it at the original query index.[size, end, start] in the heap — the start is recoverable as end - size + 1.function minInterval(intervals: number[][], queries: number[]): number[] {
// Sort intervals by start value so we can add them lazily as queries grow
intervals.sort((a, b) => a[0] - b[0]);
// Process queries in ascending order, then restore original positions
const indexedQueries = queries.map((q, i) => [q, i] as [number, number]);
indexedQueries.sort((a, b) => a[0] - b[0]);
const result: number[] = new Array(queries.length).fill(-1);
// Min-heap keyed by interval size: [size, end]
const heap: [number, number][] = [];
let ivIdx = 0;
const heapPush = (entry: [number, number]) => {
heap.push(entry);
let i = heap.length - 1;
while (i > 0) {
const p = Math.floor((i - 1) / 2);
if (heap[p][0] <= heap[i][0]) break;
[heap[p], heap[i]] = [heap[i], heap[p]];
i = p;
}
};
const heapPop = (): [number, number] => {
const top = heap[0];
const last = heap.pop()!;
if (heap.length > 0) {
heap[0] = last;
let i = 0;
while (true) {
let s = i;
const l = 2 * i + 1, r = 2 * i + 2;
if (l < heap.length && heap[l][0] < heap[s][0]) s = l;
if (r < heap.length && heap[r][0] < heap[s][0]) s = r;
if (s === i) break;
[heap[s], heap[i]] = [heap[i], heap[s]];
i = s;
}
}
return top;
};
for (const [q, origIdx] of indexedQueries) {
// Add every interval whose start <= q (it could cover q)
while (ivIdx < intervals.length && intervals[ivIdx][0] <= q) {
const [start, end] = intervals[ivIdx];
heapPush([end - start + 1, end]); // key = size, payload = end
ivIdx++;
}
// Evict intervals that ended before q (they can't cover q)
while (heap.length > 0 && heap[0][1] < q) {
heapPop();
}
// Heap top = smallest still-valid interval covering q
if (heap.length > 0) {
result[origIdx] = heap[0][0];
}
}
return result;
}The obvious-but-slow baseline: for each query, walk every interval and keep the smallest covering size — no sorting, no heap, just the definition spelled out. It is the version the offline sort + min-heap exists to speed up.
function minInterval(intervals: number[][], queries: number[]): number[] {
const result: number[] = new Array(queries.length).fill(-1);
for (let qi = 0; qi < queries.length; qi++) {
const q = queries[qi];
let best = -1; // smallest covering size seen so far; -1 = none yet
// Scan every interval; an interval [start, end] covers q iff start <= q <= end
for (let ii = 0; ii < intervals.length; ii++) {
const start = intervals[ii][0];
const end = intervals[ii][1];
if (start <= q && q <= end) {
const size = end - start + 1;
if (best === -1 || size < best) {
best = size;
}
}
}
result[qi] = best; // stays -1 if no interval covered q
}
return result;
}n intervals for every one of the q queries. When both q and n are large (each up to ~105), the ~1010 operations TLE — which is exactly what motivates sorting the queries and sweeping with a self-evicting min-heap.| "smallest interval containing each query point" | offline sort + min-heap by size |
| point queries against a set of intervals | sort queries + sweep interval pointer |
| need to evict stale heap entries lazily | pop while heap[0].end < query |
| "offline queries" — all queries known upfront | sort queries, restore original indices |
// Sort intervals by start; sort queries (keep original index)
intervals.sort((a, b) => a[0] - b[0]);
const qs = queries.map((q, i) => [q, i]).sort((a, b) => a[0] - b[0]);
const ans = new Array(queries.length).fill(-1);
// min-heap keyed by size: [size, end]
const heap: [number, number][] = [];
let iv = 0;
for (const [q, idx] of qs) {
while (iv < intervals.length && intervals[iv][0] <= q) {
heapPush([intervals[iv][1] - intervals[iv][0] + 1, intervals[iv][1]]);
iv++;
}
while (heap.length && heap[0][1] < q) heapPop(); // expired
if (heap.length) ans[idx] = heap[0][0];
}
return ans;[size, end]. What would go wrong if you keyed by end instead of size?[[1,4],[2,4],[3,6],[4,4]], query q = 4. Which intervals are in the heap just before reading the answer for q=4 (after eviction)?[[2,3],[2,5],[1,8]], queries = [2,5,6]. What is the answer for query 5?