Skip to content

Commit a5c09c4

Browse files
authored
Merge pull request #178 from BrianLusina/feat/algorithms-intervals-min-intervals
feat(algorithms, intervals): min interval to include each query
2 parents 38b0722 + 3ea5d51 commit a5c09c4

21 files changed

+232
-2
lines changed

DIRECTORY.md

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11

22
## Algorithms
33
* Arrays
4+
* Find Missing Elem
5+
* [Test Find Missing Elem](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/arrays/find_missing_elem/test_find_missing_elem.py)
46
* Intersection
57
* [Intersection One](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/arrays/intersection/intersection_one.py)
68
* [Intersection Two](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/arrays/intersection/intersection_two.py)
@@ -247,6 +249,8 @@
247249
* [Test Most Booked Meeting Rooms](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/meeting_rooms/test_most_booked_meeting_rooms.py)
248250
* Merge Intervals
249251
* [Test Merge Intervals](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/merge_intervals/test_merge_intervals.py)
252+
* Min Intervals For Queries
253+
* [Test Min Intervals To Include Query](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/min_intervals_for_queries/test_min_intervals_to_include_query.py)
250254
* Non Overlapping Intervals
251255
* [Test Non Overlapping Intervals](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/non_overlapping_intervals/test_non_overlapping_intervals.py)
252256
* Remove Intervals
@@ -324,6 +328,8 @@
324328
* Trie
325329
* Longest Word With Prefixes
326330
* [Test Longest Word With Prefixes](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/trie/longest_word_with_prefixes/test_longest_word_with_prefixes.py)
331+
* Topkfreqwords
332+
* [Test Top K Frequent Words](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/trie/topkfreqwords/test_top_k_frequent_words.py)
327333
* Two Pointers
328334
* Array 3 Pointers
329335
* [Test Array 3 Pointers](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/two_pointers/array_3_pointers/test_array_3_pointers.py)
@@ -607,6 +613,9 @@
607613
* [Types](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/trie/suffix/types.py)
608614
* [Trie](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/trie/trie.py)
609615
* [Trie Node](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/trie/trie_node.py)
616+
* Word Dictionary
617+
* [Test Word Dictionary](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/trie/word_dictionary/test_word_dictionary.py)
618+
* [Word Dictionary Trie Node](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/trees/trie/word_dictionary/word_dictionary_trie_node.py)
610619
* Tuples
611620
* [Named Tuples](https://github.com/BrianLusina/PythonSnips/blob/master/datastructures/tuples/named_tuples.py)
612621

@@ -644,8 +653,32 @@
644653
* [Visitor](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/behavioral/visitor/visitor.py)
645654
* Browser History
646655
* [Test Browser History](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/browser_history/test_browser_history.py)
656+
* Circuit Breaker
657+
* [Circuit Breaker](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/circuit_breaker/circuit_breaker.py)
647658
* Continuous Median
648659
* [Test Continuous Median Handler](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/continuous_median/test_continuous_median_handler.py)
660+
* Event Stream
661+
* [Audit Logger](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/event_stream/audit_logger.py)
662+
* [Batch Event Processor](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/event_stream/batch_event_processor.py)
663+
* [Event](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/event_stream/event.py)
664+
* [Event Priority Mapper](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/event_stream/event_priority_mapper.py)
665+
* [Event Type](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/event_stream/event_type.py)
666+
* Handlers
667+
* [Event Handler](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/event_stream/handlers/event_handler.py)
668+
* [Handlers](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/event_stream/handlers/handlers.py)
669+
* [Message Delivered Handler](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/event_stream/handlers/message_delivered_handler.py)
670+
* [Message Failed Handler](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/event_stream/handlers/message_failed_handler.py)
671+
* [Message Read Handler](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/event_stream/handlers/message_read_handler.py)
672+
* [Message Revoked Handler](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/event_stream/handlers/message_revoked_handler.py)
673+
* [Message Sent Handler](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/event_stream/handlers/message_sent_handler.py)
674+
* [Typing Handler](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/event_stream/handlers/typing_handler.py)
675+
* [Logger](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/event_stream/logger.py)
676+
* [Message Context](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/event_stream/message_context.py)
677+
* [Message State](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/event_stream/message_state.py)
678+
* [Message State Manager](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/event_stream/message_state_manager.py)
679+
* [Prioritized Event](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/event_stream/prioritized_event.py)
680+
* [Processor](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/event_stream/processor.py)
681+
* [State Transition Config](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/event_stream/state_transition_config.py)
649682
* Linked List
650683
* [Test Linked List](https://github.com/BrianLusina/PythonSnips/blob/master/design_patterns/linked_list/test_linked_list.py)
651684
* Oop
@@ -778,8 +811,6 @@
778811
* [Battleship](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/battleship/battleship.py)
779812
* Beeramid
780813
* [Test Bearamid](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/beeramid/test_bearamid.py)
781-
* Find Missing Elem
782-
* [Test Find Missing Elem](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/find_missing_elem/test_find_missing_elem.py)
783814
* Hashmap
784815
* Close Strings
785816
* [Test Close Strings](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/hashmap/close_strings/test_close_strings.py)
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

0 commit comments

Comments
 (0)