Skip to content

Commit 5bccdfc

Browse files
committed
feat(algorithms, intervals): min intervals to include each query
1 parent 38b0722 commit 5bccdfc

20 files changed

+199
-0
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# Minimum Interval to Include Each Query
2+
3+
You are given a 2D integer array intervals, where intervals[i] = [lefti, righti] describes the ith interval starting at
4+
lefti and ending at righti (inclusive). The size of an interval is defined as the number of integers it contains, or
5+
more formally righti - lefti + 1.
6+
7+
You are also given an integer array queries. The answer to the jth query is the size of the smallest interval i such
8+
that lefti <= queries[j] <= righti. If no such interval exists, the answer is -1.
9+
10+
- If at least one interval contains the query, return the minimum interval size among those intervals.
11+
- If no interval contains the query, return -1 for that query.
12+
13+
Return an array containing the answers to the queries.
14+
15+
## Examples
16+
17+
![Example 1](./images/examples/min_intervals_to_include_query_example_1.png)
18+
![Example 2](./images/examples/min_intervals_to_include_query_example_2.png)
19+
![Example 3](./images/examples/min_intervals_to_include_query_example_3.png)
20+
21+
Example 4
22+
23+
```text
24+
Input: intervals = [[1,4],[2,4],[3,6],[4,4]], queries = [2,3,4,5]
25+
Output: [3,3,1,4]
26+
Explanation: The queries are processed as follows:
27+
- Query = 2: The interval [2,4] is the smallest interval containing 2. The answer is 4 - 2 + 1 = 3.
28+
- Query = 3: The interval [2,4] is the smallest interval containing 3. The answer is 4 - 2 + 1 = 3.
29+
- Query = 4: The interval [4,4] is the smallest interval containing 4. The answer is 4 - 4 + 1 = 1.
30+
- Query = 5: The interval [3,6] is the smallest interval containing 5. The answer is 6 - 3 + 1 = 4.
31+
```
32+
33+
Example 5
34+
```text
35+
Input: intervals = [[2,3],[2,5],[1,8],[20,25]], queries = [2,19,5,22]
36+
Output: [2,-1,4,6]
37+
Explanation: The queries are processed as follows:
38+
- Query = 2: The interval [2,3] is the smallest interval containing 2. The answer is 3 - 2 + 1 = 2.
39+
- Query = 19: None of the intervals contain 19. The answer is -1.
40+
- Query = 5: The interval [2,5] is the smallest interval containing 5. The answer is 5 - 2 + 1 = 4.
41+
- Query = 22: The interval [20,25] is the smallest interval containing 22. The answer is 25 - 20 + 1 = 6.
42+
```
43+
44+
## Constraints
45+
46+
- 1 <= intervals.length <= 10^5
47+
- 1 <= queries.length <= 10^5
48+
- intervals[i].length == 2
49+
- 1 <= lefti <= righti <= 10^7
50+
- 1 <= queries[j] <= 10^7
51+
52+
## Topics
53+
54+
- Array
55+
- Binary Search
56+
- Sweep Line
57+
- Sorting
58+
- Heap (Priority Queue)
59+
60+
## Hints
61+
62+
- Is there a way to order the intervals and queries such that it takes less time to query?
63+
- Is there a way to add and remove intervals by going from the smallest query to the largest query to find the minimum
64+
size?
65+
66+
## Solution
67+
68+
The core idea is to process the queries from smallest to largest, while continuously keeping track of which intervals
69+
are active candidates for the current query. As we move forward through increasing query values, more intervals become
70+
eligible because their left endpoint is now less than equal to the current query, so we add them to a min heap. At the
71+
same time, some previously added intervals may stop being useful because their right endpoints are now less than the
72+
query, meaning they can no longer cover the query (or any future larger query), so we remove them. The heap is ordered
73+
by interval size (smallest first), and we store each interval’s right endpoint alongside its size; this lets us quickly
74+
discard expired intervals and ensures that after cleanup, the heap’s top element always represents the smallest-size
75+
interval that still covers the current query. This gives an efficient way to answer each query without scanning all
76+
intervals repeatedly.
77+
78+
The steps of the algorithm are as follows:
79+
80+
1. Sort intervals by starting point (`left`) so we can add them in the correct order as queries increase.
81+
2. Attach original indexes to queries as (`queryValue`, `originalIndex`), then sort queries by `queryValue` so we
82+
process them from smallest to largest.
83+
3. Initialize the `ans` array filled with -1
84+
4. Initialize a min heap, `heap`, that stores interval size and right endpoint as tuples `(size, right)`.
85+
5. Initialize a pointer i = 0 to walk through the sorted intervals
86+
6. For each query q in sorted order:
87+
- **Add all intervals starting at or before q**: While intervals[i].left <= q, push (size, right) into the heap and
88+
increment i.
89+
- **Remove invalid intervals**: While heap top has `right < q`, pop it (it cannot cover `q` anymore).
90+
- Answer the query:
91+
- If the heap is non-empty, the top element’s `size` is the smallest interval covering `q`, so store it in the `ans`
92+
array at the query’s original index.
93+
- Otherwise, leave `ans` as -1.
94+
7. Return ans in the original query order.
95+
96+
![Solution 1](./images/solutions/min_intervals_to_include_query_solution_1.png)
97+
![Solution 2](./images/solutions/min_intervals_to_include_query_solution_2.png)
98+
![Solution 3](./images/solutions/min_intervals_to_include_query_solution_3.png)
99+
![Solution 4](./images/solutions/min_intervals_to_include_query_solution_4.png)
100+
![Solution 5](./images/solutions/min_intervals_to_include_query_solution_5.png)
101+
![Solution 6](./images/solutions/min_intervals_to_include_query_solution_6.png)
102+
![Solution 7](./images/solutions/min_intervals_to_include_query_solution_7.png)
103+
![Solution 8](./images/solutions/min_intervals_to_include_query_solution_8.png)
104+
![Solution 19](./images/solutions/min_intervals_to_include_query_solution_9.png)
105+
![Solution 10](./images/solutions/min_intervals_to_include_query_solution_10.png)
106+
![Solution 11](./images/solutions/min_intervals_to_include_query_solution_11.png)
107+
![Solution 12](./images/solutions/min_intervals_to_include_query_solution_12.png)
108+
![Solution 13](./images/solutions/min_intervals_to_include_query_solution_13.png)
109+
![Solution 14](./images/solutions/min_intervals_to_include_query_solution_14.png)
110+
111+
## Complexity Analysis
112+
113+
### Time Complexity
114+
115+
The algorithm's time complexity is dominated by sorting and heap operations. Sorting the `intervals` array takes
116+
`O(nlog(n))`, where `n` is the number of intervals, and sorting the queries (after pairing each query with its original
117+
index) takes `O(mlog(m))`, where `m` is the number of queries.
118+
119+
During the sweep through the sorted queries, each interval is pushed into the min heap at most once, and popped at most
120+
once, and each heap operation costs `O(log(n))`, giving a total heap cost of `O(nlog(n))`.
121+
122+
Altogether, the overall time complexity is `O(n log(n) + m log(m) + n log(n))`, which is commonly simplified to
123+
`O((n+m) log(n))` (or equivalently O(n log (n) + m log(m))).
124+
125+
### Space Complexity
126+
127+
The space complexity is `O(n+m)` because, in the worst case, the heap can hold up to `n` intervals, and we also store
128+
the sorted query list and the answer array, each of size `m`.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from typing import List, Tuple
2+
import heapq
3+
4+
5+
def min_interval(intervals: List[List[int]], queries: List[int]) -> List[int]:
6+
query_len=len(queries)
7+
8+
query_indexes = list(range(query_len))
9+
query_indexes.sort(key=lambda q: queries[q])
10+
11+
# Sort intervals in ascending order. Avoiding sorting in place to keep the 'intervals' input without side effects.
12+
# Space incurred is O(n) where n is the size of intervals and time is O(n log(n))
13+
sorted_intervals = sorted(intervals, key=lambda x: x[0])
14+
intervals_len = len(sorted_intervals)
15+
16+
min_heap: List[Tuple[int, int]] = []
17+
18+
result = [-1] * query_len
19+
# Pointer to sweep through interval list
20+
i = 0
21+
22+
for query_idx in query_indexes:
23+
query = queries[query_idx]
24+
25+
# Push all intervals that start at or before query into the heap
26+
# (they are "eligible" to cover query, but might end before query)
27+
while i < intervals_len and sorted_intervals[i][0] <= query:
28+
current_interval = sorted_intervals[i]
29+
interval_start, interval_end = current_interval
30+
interval_size = interval_end - interval_start + 1
31+
heapq.heappush(min_heap, (interval_size, interval_end))
32+
i += 1
33+
34+
# Remove intervals that end before q (they can't cover q anymore)
35+
while min_heap and min_heap[0][1] < query:
36+
heapq.heappop(min_heap)
37+
38+
# If heap is not empty, top has the smallest size among intervals that cover q
39+
if min_heap:
40+
result[query_idx] = min_heap[0][0]
41+
42+
return result
100 KB
Loading
103 KB
Loading
106 KB
Loading
22.3 KB
Loading
45 KB
Loading
47.6 KB
Loading
42.6 KB
Loading
46 KB
Loading

0 commit comments

Comments
 (0)