1851. Minimum Interval to Include Each Query
Problem Description
You are given a 2D integer array intervals
where each intervals[i] = [left_i, right_i]
represents an interval that starts at left_i
and ends at right_i
(both endpoints are inclusive). The size of an interval is the number of integers it contains, calculated as right_i - left_i + 1
.
You are also given an integer array queries
. For each query queries[j]
, you need to find the smallest interval that contains the query value. Specifically, you need to find the interval i
with the minimum size such that left_i <= queries[j] <= right_i
. If no interval contains the query value, the answer is -1
.
Return an array containing the answers to all queries in their original order.
For example, if an interval is [2, 5]
, its size is 5 - 2 + 1 = 4
, and it contains the integers 2, 3, 4, and 5. If you have a query value of 3, this interval would contain it since 2 <= 3 <= 5
.
The key challenge is to efficiently find the smallest interval for each query when dealing with potentially large numbers of intervals and queries.
Intuition
The key observation is that the order in which we process queries doesn't affect the final answer - we just need to return results in the original query order. This opens up an optimization opportunity: we can sort the queries and process them in ascending order.
Why is sorting helpful? Consider processing queries from smallest to largest. As we move to larger query values, we can incrementally add intervals that might be relevant. An interval becomes relevant when its left endpoint is less than or equal to the current query value. Once an interval's left endpoint satisfies this condition for a query value x
, it will continue to satisfy it for all larger query values.
This suggests a sweep line approach: as we process queries in ascending order, we can maintain a set of "active" intervals - those whose left endpoints we've already passed. We add intervals to this set as their left endpoints become less than or equal to the current query.
However, not all active intervals are valid for the current query. An interval is only valid if it also contains the query value on the right side, meaning right_i >= query
. Intervals that end before the current query value should be removed from consideration.
To efficiently track and retrieve the smallest valid interval, we use a min heap. The heap stores intervals by their size (as the priority) along with their right endpoint. This allows us to:
- Quickly access the smallest interval (heap top)
- Remove intervals that have become invalid (right endpoint < current query)
The algorithm naturally handles the incremental nature of the problem: as we move through sorted queries, we incrementally add new intervals that become relevant and remove old intervals that are no longer valid, maintaining an optimal set of candidates at each step.
Learn more about Binary Search, Sorting, Line Sweep and Heap (Priority Queue) patterns.
Solution Approach
The implementation follows a sorting + offline query + priority queue strategy:
1. Preprocessing:
- Sort
intervals
by their left endpoints in ascending order - Create a list of tuples
(query_value, original_index)
for each query and sort by query value - Initialize answer array
ans
with-1
values (default for queries with no valid interval)
2. Main Processing Loop:
We iterate through sorted queries (x, j)
where x
is the query value and j
is its original index:
Adding relevant intervals to the heap:
while i < n and intervals[i][0] <= x: a, b = intervals[i] heappush(pq, (b - a + 1, b)) i += 1
- Use pointer
i
to track unprocessed intervals - For each interval
[a, b]
whose left endpointa <= x
, add it to the min heap - Store tuple
(interval_size, right_endpoint)
whereinterval_size = b - a + 1
- The heap is ordered by interval size (smallest first)
Removing invalid intervals:
while pq and pq[0][1] < x: heappop(pq)
- Remove intervals from the heap whose right endpoint is less than current query
x
- These intervals no longer contain the query value and are invalid
Recording the answer:
if pq: ans[j] = pq[0][0]
- If heap is not empty, the top element has the minimum size among all valid intervals
- Store this size at the original query position
j
in the answer array
3. Key Data Structures:
- Min Heap (
pq
): Maintains valid intervals ordered by size. Each element is(size, right_endpoint)
- Pointer (
i
): Tracks the next unprocessed interval to avoid redundant checks - Answer Array (
ans
): Stores results in original query order using the preserved indexj
The algorithm efficiently processes each interval and query exactly once, leveraging the sorted order to maintain only the relevant intervals in memory at any given time.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate the solution approach.
Input:
intervals = [[1, 4], [2, 3], [3, 6], [5, 7]]
queries = [4, 2, 6, 3]
Step 1: Preprocessing
First, sort intervals by left endpoint:
intervals = [[1, 4], [2, 3], [3, 6], [5, 7]]
(already sorted)
Create query tuples with original indices and sort by value:
- Original:
queries = [4, 2, 6, 3]
with indices[0, 1, 2, 3]
- Query tuples:
[(4, 0), (2, 1), (6, 2), (3, 3)]
- Sorted:
[(2, 1), (3, 3), (4, 0), (6, 2)]
Initialize answer array: ans = [-1, -1, -1, -1]
Step 2: Process Sorted Queries
Initialize: i = 0
, pq = []
(min heap)
Query (2, 1): value = 2, original index = 1
- Add intervals with left ≤ 2:
[1, 4]
: size = 4, add(4, 4)
to heap[2, 3]
: size = 2, add(2, 3)
to heap
- Heap state:
[(2, 3), (4, 4)]
- Remove intervals with right < 2: none
- Smallest valid interval: size = 2
ans[1] = 2
Query (3, 3): value = 3, original index = 3
- Add intervals with left ≤ 3:
[3, 6]
: size = 4, add(4, 6)
to heap
- Heap state:
[(2, 3), (4, 4), (4, 6)]
- Remove intervals with right < 3: none
- Smallest valid interval: size = 2 (from interval
[2, 3]
) ans[3] = 2
Query (4, 0): value = 4, original index = 0
- Add intervals with left ≤ 4: none (all processed)
- Heap state:
[(2, 3), (4, 4), (4, 6)]
- Remove intervals with right < 4:
- Remove
(2, 3)
since right endpoint 3 < 4
- Remove
- Heap state:
[(4, 4), (4, 6)]
- Smallest valid interval: size = 4 (from interval
[1, 4]
) ans[0] = 4
Query (6, 2): value = 6, original index = 2
- Add intervals with left ≤ 6:
[5, 7]
: size = 3, add(3, 7)
to heap
- Heap state:
[(3, 7), (4, 4), (4, 6)]
- Remove intervals with right < 6:
- Remove
(4, 4)
since right endpoint 4 < 6
- Remove
- Heap state:
[(3, 7), (4, 6)]
- Smallest valid interval: size = 3 (from interval
[5, 7]
) ans[2] = 3
Step 3: Return Result
Final answer array: ans = [4, 2, 3, 2]
This corresponds to:
queries[0] = 4
: smallest containing interval is[1, 4]
with size 4queries[1] = 2
: smallest containing interval is[2, 3]
with size 2queries[2] = 6
: smallest containing interval is[5, 7]
with size 3queries[3] = 3
: smallest containing interval is[2, 3]
with size 2
The key insight demonstrated is how sorting queries allows us to incrementally maintain only the relevant intervals in the heap, efficiently finding the smallest valid interval for each query.
Solution Implementation
1class Solution:
2 def minInterval(self, intervals: List[List[int]], queries: List[int]) -> List[int]:
3 """
4 Find the smallest interval size containing each query point.
5
6 Args:
7 intervals: List of intervals [start, end]
8 queries: List of query points
9
10 Returns:
11 List where each element is the size of smallest interval containing that query,
12 or -1 if no interval contains it
13 """
14 # Get the number of intervals and queries
15 num_intervals = len(intervals)
16 num_queries = len(queries)
17
18 # Sort intervals by start time for efficient processing
19 intervals.sort()
20
21 # Create indexed queries and sort by query value
22 # This allows us to process queries in order while preserving original indices
23 indexed_queries = sorted((query_value, index) for index, query_value in enumerate(queries))
24
25 # Initialize result array with -1 (default when no interval contains the query)
26 result = [-1] * num_queries
27
28 # Min heap to store intervals: (interval_size, interval_end)
29 # Smallest interval size will be at the top
30 min_heap = []
31
32 # Pointer to track which interval we're currently processing
33 interval_index = 0
34
35 # Process each query in sorted order
36 for query_value, original_index in indexed_queries:
37 # Add all intervals that start before or at the current query point
38 while interval_index < num_intervals and intervals[interval_index][0] <= query_value:
39 start, end = intervals[interval_index]
40 interval_size = end - start + 1
41 heappush(min_heap, (interval_size, end))
42 interval_index += 1
43
44 # Remove intervals from heap that end before the current query point
45 # These intervals don't contain the query point
46 while min_heap and min_heap[0][1] < query_value:
47 heappop(min_heap)
48
49 # If heap is not empty, the top element is the smallest valid interval
50 if min_heap:
51 result[original_index] = min_heap[0][0]
52
53 return result
54
1class Solution {
2 public int[] minInterval(int[][] intervals, int[] queries) {
3 int numIntervals = intervals.length;
4 int numQueries = queries.length;
5
6 // Sort intervals by start time
7 Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
8
9 // Create array to store query value with its original index
10 int[][] queriesWithIndex = new int[numQueries][2];
11 for (int i = 0; i < numQueries; i++) {
12 queriesWithIndex[i] = new int[] {queries[i], i};
13 }
14
15 // Sort queries by their values
16 Arrays.sort(queriesWithIndex, (a, b) -> a[0] - b[0]);
17
18 // Initialize result array with -1 (default for no interval found)
19 int[] result = new int[numQueries];
20 Arrays.fill(result, -1);
21
22 // Min heap to store intervals by their length
23 // Each element: [interval length, interval end]
24 PriorityQueue<int[]> minHeap = new PriorityQueue<>((a, b) -> a[0] - b[0]);
25
26 int intervalIndex = 0;
27
28 // Process each query in sorted order
29 for (int[] queryInfo : queriesWithIndex) {
30 int queryValue = queryInfo[0];
31 int originalIndex = queryInfo[1];
32
33 // Add all intervals that start before or at the query point
34 while (intervalIndex < numIntervals && intervals[intervalIndex][0] <= queryValue) {
35 int startTime = intervals[intervalIndex][0];
36 int endTime = intervals[intervalIndex][1];
37 int intervalLength = endTime - startTime + 1;
38
39 // Store interval length and end time in heap
40 minHeap.offer(new int[] {intervalLength, endTime});
41 intervalIndex++;
42 }
43
44 // Remove intervals that end before the query point
45 while (!minHeap.isEmpty() && minHeap.peek()[1] < queryValue) {
46 minHeap.poll();
47 }
48
49 // If valid intervals exist, get the smallest one
50 if (!minHeap.isEmpty()) {
51 result[originalIndex] = minHeap.peek()[0];
52 }
53 }
54
55 return result;
56 }
57}
58
1class Solution {
2public:
3 vector<int> minInterval(vector<vector<int>>& intervals, vector<int>& queries) {
4 int numIntervals = intervals.size();
5 int numQueries = queries.size();
6
7 // Sort intervals by their start points
8 sort(intervals.begin(), intervals.end());
9
10 // Create pairs of (query value, original index) to maintain order after sorting
11 using QueryPair = pair<int, int>;
12 vector<QueryPair> sortedQueries;
13 for (int i = 0; i < numQueries; ++i) {
14 sortedQueries.emplace_back(queries[i], i);
15 }
16
17 // Sort queries by their values
18 sort(sortedQueries.begin(), sortedQueries.end());
19
20 // Initialize result array with -1 (no interval found)
21 vector<int> result(numQueries, -1);
22
23 // Min heap to store (interval length, interval end point)
24 // Sorted by interval length (smallest first)
25 using IntervalPair = pair<int, int>;
26 priority_queue<IntervalPair, vector<IntervalPair>, greater<IntervalPair>> minHeap;
27
28 int intervalIndex = 0;
29
30 // Process each query in sorted order
31 for (auto& [queryValue, originalIndex] : sortedQueries) {
32 // Add all intervals that start before or at the current query point
33 while (intervalIndex < numIntervals && intervals[intervalIndex][0] <= queryValue) {
34 int startPoint = intervals[intervalIndex][0];
35 int endPoint = intervals[intervalIndex][1];
36 int intervalLength = endPoint - startPoint + 1;
37
38 // Store interval length and end point in the heap
39 minHeap.emplace(intervalLength, endPoint);
40 ++intervalIndex;
41 }
42
43 // Remove intervals that end before the current query point
44 while (!minHeap.empty() && minHeap.top().second < queryValue) {
45 minHeap.pop();
46 }
47
48 // If there's a valid interval, record its length
49 if (!minHeap.empty()) {
50 result[originalIndex] = minHeap.top().first;
51 }
52 }
53
54 return result;
55 }
56};
57
1function minInterval(intervals: number[][], queries: number[]): number[] {
2 const numIntervals = intervals.length;
3 const numQueries = queries.length;
4
5 // Sort intervals by their start points
6 intervals.sort((a, b) => a[0] - b[0]);
7
8 // Create pairs of [query value, original index] to maintain order after sorting
9 const sortedQueries: [number, number][] = [];
10 for (let i = 0; i < numQueries; i++) {
11 sortedQueries.push([queries[i], i]);
12 }
13
14 // Sort queries by their values
15 sortedQueries.sort((a, b) => a[0] - b[0]);
16
17 // Initialize result array with -1 (no interval found)
18 const result: number[] = new Array(numQueries).fill(-1);
19
20 // Min heap to store [interval length, interval end point]
21 // Sorted by interval length (smallest first)
22 const minHeap: [number, number][] = [];
23
24 // Helper function to maintain min heap property after insertion
25 const heapPush = (item: [number, number]): void => {
26 minHeap.push(item);
27 let currentIndex = minHeap.length - 1;
28
29 // Bubble up to maintain heap property
30 while (currentIndex > 0) {
31 const parentIndex = Math.floor((currentIndex - 1) / 2);
32 if (minHeap[currentIndex][0] < minHeap[parentIndex][0]) {
33 [minHeap[currentIndex], minHeap[parentIndex]] = [minHeap[parentIndex], minHeap[currentIndex]];
34 currentIndex = parentIndex;
35 } else {
36 break;
37 }
38 }
39 };
40
41 // Helper function to remove and return the minimum element
42 const heapPop = (): void => {
43 if (minHeap.length === 0) return;
44
45 minHeap[0] = minHeap[minHeap.length - 1];
46 minHeap.pop();
47
48 if (minHeap.length === 0) return;
49
50 // Bubble down to maintain heap property
51 let currentIndex = 0;
52 while (true) {
53 const leftChild = 2 * currentIndex + 1;
54 const rightChild = 2 * currentIndex + 2;
55 let smallest = currentIndex;
56
57 if (leftChild < minHeap.length && minHeap[leftChild][0] < minHeap[smallest][0]) {
58 smallest = leftChild;
59 }
60 if (rightChild < minHeap.length && minHeap[rightChild][0] < minHeap[smallest][0]) {
61 smallest = rightChild;
62 }
63
64 if (smallest !== currentIndex) {
65 [minHeap[currentIndex], minHeap[smallest]] = [minHeap[smallest], minHeap[currentIndex]];
66 currentIndex = smallest;
67 } else {
68 break;
69 }
70 }
71 };
72
73 let intervalIndex = 0;
74
75 // Process each query in sorted order
76 for (const [queryValue, originalIndex] of sortedQueries) {
77 // Add all intervals that start before or at the current query point
78 while (intervalIndex < numIntervals && intervals[intervalIndex][0] <= queryValue) {
79 const startPoint = intervals[intervalIndex][0];
80 const endPoint = intervals[intervalIndex][1];
81 const intervalLength = endPoint - startPoint + 1;
82
83 // Store interval length and end point in the heap
84 heapPush([intervalLength, endPoint]);
85 intervalIndex++;
86 }
87
88 // Remove intervals that end before the current query point
89 while (minHeap.length > 0 && minHeap[0][1] < queryValue) {
90 heapPop();
91 }
92
93 // If there's a valid interval, record its length
94 if (minHeap.length > 0) {
95 result[originalIndex] = minHeap[0][0];
96 }
97 }
98
99 return result;
100}
101
Time and Space Complexity
Time Complexity: O(n × log n + m × log m)
The time complexity breaks down as follows:
- Sorting intervals takes
O(n × log n)
wheren
is the number of intervals - Creating and sorting the queries list with indices takes
O(m × log m)
wherem
is the number of queries - The main loop iterates through all
m
queries:- The inner while loop for adding intervals runs at most
n
times total across all iterations (each interval is added once) - Each heap push operation takes
O(log n)
time - The while loop for removing expired intervals also runs at most
n
times total across all iterations - Each heap pop operation takes
O(log n)
time
- The inner while loop for adding intervals runs at most
- Total heap operations: at most
n
pushes andn
pops, givingO(n × log n)
- Overall:
O(n × log n + m × log m + n × log n)
=O(n × log n + m × log m)
Space Complexity: O(n + m)
The space complexity consists of:
- The sorted queries list with indices:
O(m)
- The answer array:
O(m)
- The priority queue can contain at most
n
intervals:O(n)
- Total:
O(n + m)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Not Handling the Original Query Order
Pitfall: A common mistake is sorting the queries for efficient processing but forgetting to preserve their original indices, leading to results being returned in the wrong order.
Wrong approach:
# Sorting queries without preserving indices queries.sort() # Process queries... # Results will be in sorted query order, not original order!
Correct approach:
# Create indexed queries to preserve original positions
indexed_queries = sorted((query_value, index) for index, query_value in enumerate(queries))
# Use original_index to place results in correct position
result[original_index] = min_heap[0][0]
2. Incorrect Heap Cleanup Logic
Pitfall: Using the wrong comparison when removing invalid intervals from the heap. The condition should check if the interval's right endpoint is less than the query (not less than or equal).
Wrong approach:
# This removes intervals that could still be valid while min_heap and min_heap[0][1] <= query_value: # Wrong! heappop(min_heap)
Correct approach:
# Only remove intervals that end before the query point while min_heap and min_heap[0][1] < query_value: # Correct! heappop(min_heap)
3. Reprocessing Intervals for Each Query
Pitfall: Resetting the interval index for each query, causing intervals to be processed multiple times and degrading performance to O(n*m).
Wrong approach:
for query_value, original_index in indexed_queries: interval_index = 0 # Resetting for each query - inefficient! while interval_index < num_intervals and intervals[interval_index][0] <= query_value: # Process interval...
Correct approach:
interval_index = 0 # Initialize once outside the loop for query_value, original_index in indexed_queries: # Continue from where we left off while interval_index < num_intervals and intervals[interval_index][0] <= query_value: # Process interval... interval_index += 1 # Move forward, never reset
4. Storing Incorrect Information in the Heap
Pitfall: Storing only the interval size in the heap without the right endpoint, making it impossible to determine when an interval becomes invalid.
Wrong approach:
# Missing right endpoint information heappush(min_heap, interval_size) # Can't check validity later!
Correct approach:
# Store both size and right endpoint heappush(min_heap, (interval_size, end)) # Now we can check if end < query_value to determine validity
5. Off-by-One Error in Size Calculation
Pitfall: Forgetting that intervals are inclusive on both ends, leading to incorrect size calculation.
Wrong approach:
interval_size = end - start # Missing the +1 for inclusive endpoints
Correct approach:
interval_size = end - start + 1 # Correct for inclusive intervals
These pitfalls can cause incorrect results or significant performance degradation. The key insight is that the algorithm relies on maintaining state across queries (the interval pointer and heap) while properly tracking the original query order.
What are the two properties the problem needs to have for dynamic programming to be applicable? (Select 2)
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Line Sweep Introduction The primary motivation for this algorithm is for geometry related problems that we encounter in computer science Deeply rooted in geometric problem solving it has become an essential tool for tackling questions related to shapes lines or points on a Cartesian plane At its heart the line
Want a Structured Path to Master System Design Too? Don’t Miss This!