1851. Minimum Interval to Include Each Query - Explanation
Description
You are given a 2D integer array intervals, where intervals[i] = [left_i, right_i] represents the ith interval starting at left_i and ending at right_i (inclusive).
You are also given an integer array of query points queries. The result of query[j] is the length of the shortest interval i such that left_i <= queries[j] <= right_i. If no such interval exists, the result of this query is -1.
Return an array output where output[j] is the result of query[j].
Note: The length of an interval is calculated as right_i - left_i + 1.
Example 1:
Input: intervals = [[1,3],[2,3],[3,7],[6,6]], queries = [2,3,1,7,6,8]
Output: [2,2,3,5,1,-1]Explanation:
- Query = 2: The interval
[2,3]is the smallest one containing 2, it's length is 2. - Query = 3: The interval
[2,3]is the smallest one containing 3, it's length is 2. - Query = 1: The interval
[1,3]is the smallest one containing 1, it's length is 3. - Query = 7: The interval
[3,7]is the smallest one containing 7, it's length is 5. - Query = 6: The interval
[6,6]is the smallest one containing 6, it's length is 1. - Query = 8: There is no interval containing 8.
Constraints:
1 <= intervals.length <= 10001 <= queries.length <= 10001 <= left_i <= right_i <= 100001 <= queries[j] <= 10000
Topics
Recommended Time & Space Complexity
You should aim for a solution as good or better than O(nlogn + mlogm) time and O(n + m) space, where m is the size of the array queries and n is the size of the array intervals.
Hint 1
A brute force approach would be to iterate through each query and, for each query, check all intervals to find the result. This would be an O(m * n) solution. Can you think of a better way? Maybe processing the queries in sorted order could help.
Hint 2
We sort the intervals by start value and process the queries in ascending order. Using a pointer i, we add intervals to a min-heap while their start values are less than or equal to the query, storing their end values and sizes.
Hint 3
The min-heap is ordered by interval size. We remove elements from the heap while the top element’s end value is less than the current query. The result for the query is the top element’s size if the heap is non-empty; otherwise, it is -1.
Prerequisites
Before attempting this problem, you should be comfortable with:
- Sorting - Sorting intervals and queries to process them in order
- Min Heap / Priority Queue - Efficiently retrieving the smallest interval covering a query
- Sweep Line Algorithm - Processing events (interval starts, ends, queries) in sorted order
- Segment Trees with Lazy Propagation - Advanced technique for range minimum queries and updates
- Coordinate Compression - Mapping large coordinate values to smaller indices
1. Brute Force
Intuition
For each query value q, we want to find the smallest interval length among all intervals [l, r] that contain q (meaning l <= q <= r).
If no interval contains q, we return -1.
The brute force idea is very direct:
- handle queries one by one
- for a query, scan through every interval
- whenever an interval covers the query, compute its length
r - l + 1 - keep the minimum length seen
Algorithm
- Initialize an empty list
res. - For each query
qinqueries:- set
cur = -1to represent "no covering interval found yet"
- set
- Iterate through every interval
[l, r]:- if
l <= q <= r, then the interval coversq - compute its length
len = r - l + 1 - update
curif:cur == -1(first valid interval), orlen < cur(found a smaller covering interval)
- if
- Append
curtoresfor this query. - After all queries are processed, return
res.
class Solution:
def minInterval(self, intervals: List[List[int]], queries: List[int]) -> List[int]:
n = len(intervals)
res = []
for q in queries:
cur = -1
for l, r in intervals:
if l <= q <= r:
if cur == -1 or (r - l + 1) < cur:
cur = r - l + 1
res.append(cur)
return resTime & Space Complexity
- Time complexity:
- Space complexity:
- extra space.
- space for the output array.
Where is the length of the array and is the length of the array .
2. Sweep Line Algorithm
Intuition
For each query value q, we want the length of the smallest interval [l, r] that contains q (l <= q <= r). If none exists, the answer is -1.
A sweep line approach processes everything (interval starts, interval ends, and queries) in sorted order of time.
While sweeping from left to right, we maintain a data structure of “currently active intervals” (intervals that have started but have not ended yet).
For a query time q, the answer is simply the smallest interval length among the active intervals.
To get that smallest length quickly, we use a min-heap ordered by interval size.
Because intervals can end later, we also need a way to remove expired intervals:
- when an interval ends, we mark it as inactive
- when answering a query, we pop inactive intervals from the heap until the top is active
Algorithm
- Build a list of events:
- For each interval
[start, end]:- Add a start event at
startcontaining its length and index - Add an end event at
endcontaining its index
- Add a start event at
- For each query
q:- Add a query event at
qcontaining the query's original position
- Add a query event at
- For each interval
- Sort all events by
(time, type)so they are processed in time order. - Use:
- a min-heap
sizesstoring(interval_length, interval_index)for active intervals - an array
inactiveto mark intervals that have ended - an output array
ansinitialized with-1
- a min-heap
- Sweep through events in sorted order:
- If it is an interval start:
- push
(length, idx)into the heap
- push
- If it is an interval end:
- mark that interval index as inactive
- If it is a query:
- pop from the heap while the top interval is inactive
- if heap is not empty, the top length is the smallest covering interval store it in
ans - otherwise leave
-1
- If it is an interval start:
- Return
ansin the original query order.
class Solution:
def minInterval(self, intervals: List[List[int]], queries: List[int]) -> List[int]:
events = []
# Create events for intervals
for idx, (start, end) in enumerate(intervals):
events.append((start, 0, end - start + 1, idx))
events.append((end, 2, end - start + 1, idx))
# Create events for queries
for i, q in enumerate(queries):
events.append((q, 1, i))
# Sort by time and type (end before query)
events.sort(key=lambda x: (x[0], x[1]))
# Min heap storing [size, index]
sizes = []
ans = [-1] * len(queries)
inactive = [False] * len(intervals)
for time, type, *rest in events:
if type == 0: # Interval start
interval_size, idx = rest
heapq.heappush(sizes, (interval_size, idx))
elif type == 2: #Interval end
idx = rest[1]
inactive[idx] = True
else: # Query
query_idx = rest[0]
while sizes and inactive[sizes[0][1]]:
heapq.heappop(sizes)
if sizes:
ans[query_idx] = sizes[0][0]
return ansTime & Space Complexity
- Time complexity:
- Space complexity:
Where is the length of the array and is the length of the array .
3. Min Heap
Intuition
For each query q, we want the length of the smallest interval [l, r] such thatl ≤ q ≤ r. If no interval covers q, the answer is -1.
A very efficient way to do this is:
- process queries in sorted order
- as queries increase, we add intervals whose start ≤ q
- among those active intervals, remove any that end before
q - the smallest valid interval is always at the top of a min heap
The heap is ordered by interval length, so the smallest covering interval is easy to find.
Algorithm
- Sort
intervalsby their start value. - Sort the
queries, but keep their original order for the final answer. - Initialize:
- a min heap
minHeapstoring(interval_length, interval_end) - an index
i = 0to iterate through intervals - a map
resto store answers for each query
- a min heap
- For each query
qin sorted order:- While there are intervals whose start
≤ q:- push
(r - l + 1, r)into the heap - move
iforward
- push
- While the heap is not empty and the top interval ends before
q:- pop it from the heap
- If the heap is not empty:
- the top element's length is the answer for
q
- the top element's length is the answer for
- Otherwise:
- the answer for
qis-1
- the answer for
- Store the result for
q
- While there are intervals whose start
- Return the answers in the original query order.
class Solution:
def minInterval(self, intervals: List[List[int]], queries: List[int]) -> List[int]:
intervals.sort()
minHeap = []
res = {}
i = 0
for q in sorted(queries):
while i < len(intervals) and intervals[i][0] <= q:
l, r = intervals[i]
heapq.heappush(minHeap, (r - l + 1, r))
i += 1
while minHeap and minHeap[0][1] < q:
heapq.heappop(minHeap)
res[q] = minHeap[0][0] if minHeap else -1
return [res[q] for q in queries]Time & Space Complexity
- Time complexity:
- Space complexity:
Where is the length of the array and is the length of the array .
4. Min Segment Tree (Lazy Propagation)
Intuition
For each query q, we need the length of the smallest interval [l, r] such that l <= q <= r.
If no interval covers q, the answer is -1.
This solution treats the number line as a set of discrete important points (all interval endpoints and all queries).
Then it uses a segment tree that supports:
- Range update: “For every point covered by this interval, the best (minimum) interval length might become smaller.”
- Point query: “At this query point, what is the minimum interval length that covers it?”
Since many intervals update large ranges, we use lazy propagation to apply updates efficiently without touching every point in the range.
Because coordinates can be large, we first do coordinate compression:
- map every unique coordinate to a compact index
0..M-1 - this allows the segment tree to work on a small index range
Algorithm
- Collect all important points
- Add every interval start and end
- Add every query value
- Coordinate compress
- Sort and deduplicate points
- Create a map
compress[value] -> index
- Build a min segment tree with lazy propagation
- Each node stores the minimum interval length applied to that segment
- Lazy values store “pending minimum updates” that still need to be pushed down
- Apply each interval as a range update
- For interval
[l, r]:- convert to compressed indices
[L, R] - compute its length
len = r - l + 1 - update the segment tree on range
[L, R]withlenusing:tree[node] = min(tree[node], len)
- lazy propagation ensures this is efficient
- convert to compressed indices
- For interval
- Answer each query with a point query
- Convert query
qto compressed indexidx - Query the segment tree at
idxto get the minimum covering length - If the value is still infinity, return
-1(no interval covers it)
- Convert query
- Return answers in the original query order.
class SegmentTree:
def __init__(self, N):
self.n = N
self.tree = [float('inf')] * (4 * N)
self.lazy = [float('inf')] * (4 * N)
def propagate(self, treeidx, lo, hi):
if self.lazy[treeidx] != float('inf'):
self.tree[treeidx] = min(self.tree[treeidx], self.lazy[treeidx])
if lo != hi:
self.lazy[2 * treeidx + 1] = min(self.lazy[2 * treeidx + 1], self.lazy[treeidx])
self.lazy[2 * treeidx + 2] = min(self.lazy[2 * treeidx + 2], self.lazy[treeidx])
self.lazy[treeidx] = float('inf')
def update(self, treeidx, lo, hi, left, right, val):
self.propagate(treeidx, lo, hi)
if lo > right or hi < left:
return
if lo >= left and hi <= right:
self.lazy[treeidx] = min(self.lazy[treeidx], val)
self.propagate(treeidx, lo, hi)
return
mid = (lo + hi) // 2
self.update(2 * treeidx + 1, lo, mid, left, right, val)
self.update(2 * treeidx + 2, mid + 1, hi, left, right, val)
self.tree[treeidx] = min(self.tree[2 * treeidx + 1], self.tree[2 * treeidx + 2])
def query(self, treeidx, lo, hi, idx):
self.propagate(treeidx, lo, hi)
if lo == hi:
return self.tree[treeidx]
mid = (lo + hi) // 2
if idx <= mid:
return self.query(2 * treeidx + 1, lo, mid, idx)
else:
return self.query(2 * treeidx + 2, mid + 1, hi, idx)
def update_range(self, left, right, val):
self.update(0, 0, self.n - 1, left, right, val)
def query_point(self, idx):
return self.query(0, 0, self.n - 1, idx)
class Solution:
def minInterval(self, intervals: List[List[int]], queries: List[int]) -> List[int]:
points = []
for interval in intervals:
points.append(interval[0])
points.append(interval[1])
for q in queries:
points.append(q)
# Compress the coordinates
points = sorted(set(points))
compress = {points[i]: i for i in range(len(points))}
# Lazy Segment Tree
segTree = SegmentTree(len(points))
for interval in intervals:
start = compress[interval[0]]
end = compress[interval[1]]
length = interval[1] - interval[0] + 1
segTree.update_range(start, end, length)
ans = []
for q in queries:
idx = compress[q]
# query for minSize
res = segTree.query_point(idx)
ans.append(res if res != float('inf') else -1)
return ansTime & Space Complexity
- Time complexity:
- Space complexity:
- extra space.
- space for the output array.
Where is the length of the array , is the length of the array and is the number of unique points.
Common Pitfalls
Losing Original Query Order After Sorting
When sorting queries for efficient processing, the original indices must be preserved. Returning results in sorted query order instead of the original order produces incorrect output. Always pair each query with its original index before sorting.
Forgetting to Remove Expired Intervals from the Heap
Intervals that have ended (their right endpoint is less than the current query) must be removed from the heap before answering. Failing to pop expired intervals means the heap may return an interval that does not actually contain the query point.
Incorrect Event Ordering in Sweep Line Approach
When multiple events occur at the same coordinate, processing order matters. Interval end events should typically be processed before query events at the same point to ensure intervals ending exactly at the query point are still considered active.
Using Wrong Interval Length Calculation
The length of interval [l, r] is r - l + 1, not r - l. This off-by-one error affects which interval is considered smallest and leads to incorrect results when intervals differ by exactly one unit.
Not Handling Queries Outside All Intervals
When no interval covers a query, the answer must be -1. Forgetting to initialize the result array with -1 or not checking for an empty heap after removing expired intervals causes undefined or incorrect values to be returned.
Sign in to join the discussion