diff --git a/DIRECTORY.md b/DIRECTORY.md index 37c48120..49dafb19 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -95,6 +95,8 @@ * Intervals * Insert Interval * [Test Insert Interval](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/insert_interval/test_insert_interval.py) + * Interval Intersection + * [Test Intervals Intersection](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/interval_intersection/test_intervals_intersection.py) * Meeting Rooms * [Test Min Meeting Rooms](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/meeting_rooms/test_min_meeting_rooms.py) * Merge Intervals diff --git a/algorithms/intervals/interval_intersection/README.md b/algorithms/intervals/interval_intersection/README.md new file mode 100644 index 00000000..53257209 --- /dev/null +++ b/algorithms/intervals/interval_intersection/README.md @@ -0,0 +1,118 @@ +# Interval List Intersections + +Given two lists of closed intervals, interval_list_a and interval_list_b, return the intersection of the two interval +lists. + +> A closed interval [start, end] (with start <= end) includes all real numbers x such that start <= x <= end. + +Each interval in the lists has its own start and end time and is represented as [start, end]. Specifically: + +- interval_list_a[i] = [starti, endi] +- interval_list_b[j] = [startj, endj] + +The intersection of two closed intervals i and j is either: +- An empty set, if they do not overlap, or +- A closed interval [max(starti, startj), min(endi, endj)] if they do overlap. + +Also, each list of intervals is pairwise disjoint(intervals within a list don't overlap) and in sorted order. + +## Constraints + +- 0 <= `interval_list_a.length`, `interval_list_b.length` <= 1000 +- `interval_list_a.length` + `interval_list_b.length` >= 1 +- 0 <= `starti` < `endi` <= 10^9 +- `endi` < `start(i+1)` +- 0 <= `startj` < `endj` <= 10^9 +- `endj` < `start(j+1)` + +## Examples + +![Example 1](./images/examples/interval_intersection_example_1.png) +![Example 2](./images/examples/interval_intersection_example_2.png) +![Example 3](./images/examples/interval_intersection_example_3.png) +![Example 4](./images/examples/interval_intersection_example_4.png) +![Example 5](./images/examples/interval_intersection_example_5.png) + +## Topics + +- Two pointers +- Intervals + +## Solutions + +1. [Naive Approach](#naive-approach) +1. [Using Intervals](#optimized-approach-using-intervals) + +### Naive Approach + +The naive approach for this problem is to use a nested loop for finding intersecting intervals. + +The outer loop will iterate for every interval in interval_list_a and the inner loop will search for any intersecting +interval in the interval_list_b. + +If such an interval exists, we add it to the intersections list. + +Since we are using nested loops, the time complexity for this naive approach will be O(n*m), where n is the length of +intervalsA and m is the length of intervalsB. + +### Optimized approach using intervals + +The essence of this approach is to leverage two key advantages: first, the lists of intervals are sorted, and second, +the result requires comparing intervals to check for overlap. The algorithm works by iterating through both sorted lists +of intervals simultaneously, identifying intersections between intervals from the two lists. At each step, it compares +the current intervals from both lists and determines whether there is an intersection by examining the endpoints of the +intervals. It adds the intersecting interval to the result list if an intersection exists. To efficiently navigate +through the intervals, the algorithm adjusts pointers based on the positions of the intervals’ endpoints, ensuring that +it covers all possible intersections. The algorithm accurately computes the interval list intersection by systematically +traversing the lists, identifying intersections, and adding them to the results list, the algorithm accurately computes +the interval list intersection. + +The algorithm to solve this problem is as follows: + +- We’ll use two indexes, i and j, to iterate through the intervals in both lists, `interval_list_a` and `interval_list_b`, + respectively. +- To check whether there’s any intersecting point among the given intervals: + - Take the starting times of the first pair of intervals from both lists and check which occurs later, storing it in + a variable, say start. + - Also, compare the ending times of the same pair of intervals from both lists and store the minimum end time in + another variable, say, end. + +![Solution 1](./images/solutions/interval_intersection_solution_1.png) +![Solution 2](./images/solutions/interval_intersection_solution_2.png) + +- Next, we will check if `interval_list_a[i]` and `interval_list_b[j]` overlap by comparing the start and end times. + - If the times overlap, then the intersecting time interval will be added to the resultant list, that is, intersections. + - After the comparison, we need to move forward in one of the two input lists. The decision is taken based on which + of the two intervals being compared ends earlier. If the interval that ends first is in `interval_list_a`, we move + forward in that list, else, we move forward in `interval_list_b`. + +The illustrations below show the key steps of the solution. + +![Solution 3](./images/solutions/interval_intersection_solution_3.png) +![Solution 4](./images/solutions/interval_intersection_solution_4.png) +![Solution 5](./images/solutions/interval_intersection_solution_5.png) +![Solution 6](./images/solutions/interval_intersection_solution_6.png) +![Solution 7](./images/solutions/interval_intersection_solution_7.png) +![Solution 8](./images/solutions/interval_intersection_solution_8.png) + +#### Solution summary + +Let’s briefly discuss the approach that we have used to solve the above mentioned problem: + +- Set two pointers, i and j, at the beginning of both lists, respectively, for their iteration. +- While iterating, find the latest starting time and the earliest ending time for each pair of intervals + `interval_list_a[i]` and `interval_list_b[j]`. +- If the latest starting time is less than or equal to the earliest ending time, store it as an intersection. +- Increment the pointer (i or j) of the list having the smaller end time of the current interval. +- Keep iterating until either list is fully traversed. +- Return the list of intersections. + +#### Time Complexity + +The time complexity is `O(n+m)`, where n and m are the number of meetings in `interval_list_a` and `interval_list_b`, +respectively. + +#### Space Complexity + +The space complexity is `O(1)` as only a fixed amount of memory is consumed by a few temporary variables for computations +performed by the algorithm. diff --git a/algorithms/intervals/interval_intersection/__init__.py b/algorithms/intervals/interval_intersection/__init__.py new file mode 100644 index 00000000..c434d625 --- /dev/null +++ b/algorithms/intervals/interval_intersection/__init__.py @@ -0,0 +1,60 @@ +from typing import List + + +def intervals_intersection( + interval_list_a: List[List[int]], interval_list_b: List[List[int]] +) -> List[List[int]]: + """ + This function finds the intersections between interval_list_a and interval_list_b and returns the list of + intersections. + + Time Complexity is O(n+m): where n is the length of list a and m is the length of list b + Space Complexity is O(1) for auxiliary space as no extra space is required other than the pointers used to move along + both lists. However, the resultant list returned can have a worst case of O(n+m) in the case we have either list + having multiple intervals that are all intersections in the other list. + + Args: + interval_list_a(list): list 'a' of intervals. + interval_list_b(list): list 'b' of intervals. + Returns: + list: list of intersected intervals + """ + if not interval_list_a and not interval_list_b: + return [] + # pointers that will move along the interval lists, i moves along the intervals on interval_list_a, while j moves + # along the intervals on interval_list_b + i, j = 0, 0 + + # the final result list + intersected_intervals = [] + + interval_list_a_len = len(interval_list_a) + interval_list_b_len = len(interval_list_b) + + # while loop will break whenever either of the lists ends + while i < interval_list_a_len and j < interval_list_b_len: + # Let's check if interval_list_a[i] intersects interval_list_b[j] + interval_a = interval_list_a[i] + interval_b = interval_list_b[j] + + # extract the start and end times of each interval + start_a, end_a = interval_a + start_b, end_b = interval_b + + # Get the potential startpoint and endpoint of the closed interval intersection + closed_interval_start = max(start_a, start_b) + closed_interval_end = min(end_a, end_b) + + # if this is an actual intersection + if closed_interval_start <= closed_interval_end: + # add it to the list + closed_interval = [closed_interval_start, closed_interval_end] + intersected_intervals.append(closed_interval) + + # Move forward in the list whose interval ends earlier + if end_a <= end_b: + i += 1 + else: + j += 1 + + return intersected_intervals diff --git a/algorithms/intervals/interval_intersection/images/examples/interval_intersection_example_1.png b/algorithms/intervals/interval_intersection/images/examples/interval_intersection_example_1.png new file mode 100644 index 00000000..5bd169d4 Binary files /dev/null and b/algorithms/intervals/interval_intersection/images/examples/interval_intersection_example_1.png differ diff --git a/algorithms/intervals/interval_intersection/images/examples/interval_intersection_example_2.png b/algorithms/intervals/interval_intersection/images/examples/interval_intersection_example_2.png new file mode 100644 index 00000000..c51bebaf Binary files /dev/null and b/algorithms/intervals/interval_intersection/images/examples/interval_intersection_example_2.png differ diff --git a/algorithms/intervals/interval_intersection/images/examples/interval_intersection_example_3.png b/algorithms/intervals/interval_intersection/images/examples/interval_intersection_example_3.png new file mode 100644 index 00000000..17f8c8f9 Binary files /dev/null and b/algorithms/intervals/interval_intersection/images/examples/interval_intersection_example_3.png differ diff --git a/algorithms/intervals/interval_intersection/images/examples/interval_intersection_example_4.png b/algorithms/intervals/interval_intersection/images/examples/interval_intersection_example_4.png new file mode 100644 index 00000000..261a6219 Binary files /dev/null and b/algorithms/intervals/interval_intersection/images/examples/interval_intersection_example_4.png differ diff --git a/algorithms/intervals/interval_intersection/images/examples/interval_intersection_example_5.png b/algorithms/intervals/interval_intersection/images/examples/interval_intersection_example_5.png new file mode 100644 index 00000000..582214d1 Binary files /dev/null and b/algorithms/intervals/interval_intersection/images/examples/interval_intersection_example_5.png differ diff --git a/algorithms/intervals/interval_intersection/images/solutions/interval_intersection_solution_1.png b/algorithms/intervals/interval_intersection/images/solutions/interval_intersection_solution_1.png new file mode 100644 index 00000000..94ef28e5 Binary files /dev/null and b/algorithms/intervals/interval_intersection/images/solutions/interval_intersection_solution_1.png differ diff --git a/algorithms/intervals/interval_intersection/images/solutions/interval_intersection_solution_2.png b/algorithms/intervals/interval_intersection/images/solutions/interval_intersection_solution_2.png new file mode 100644 index 00000000..4fbbbd40 Binary files /dev/null and b/algorithms/intervals/interval_intersection/images/solutions/interval_intersection_solution_2.png differ diff --git a/algorithms/intervals/interval_intersection/images/solutions/interval_intersection_solution_3.png b/algorithms/intervals/interval_intersection/images/solutions/interval_intersection_solution_3.png new file mode 100644 index 00000000..937d111b Binary files /dev/null and b/algorithms/intervals/interval_intersection/images/solutions/interval_intersection_solution_3.png differ diff --git a/algorithms/intervals/interval_intersection/images/solutions/interval_intersection_solution_4.png b/algorithms/intervals/interval_intersection/images/solutions/interval_intersection_solution_4.png new file mode 100644 index 00000000..95f02a71 Binary files /dev/null and b/algorithms/intervals/interval_intersection/images/solutions/interval_intersection_solution_4.png differ diff --git a/algorithms/intervals/interval_intersection/images/solutions/interval_intersection_solution_5.png b/algorithms/intervals/interval_intersection/images/solutions/interval_intersection_solution_5.png new file mode 100644 index 00000000..24ab49c6 Binary files /dev/null and b/algorithms/intervals/interval_intersection/images/solutions/interval_intersection_solution_5.png differ diff --git a/algorithms/intervals/interval_intersection/images/solutions/interval_intersection_solution_6.png b/algorithms/intervals/interval_intersection/images/solutions/interval_intersection_solution_6.png new file mode 100644 index 00000000..9fad5929 Binary files /dev/null and b/algorithms/intervals/interval_intersection/images/solutions/interval_intersection_solution_6.png differ diff --git a/algorithms/intervals/interval_intersection/images/solutions/interval_intersection_solution_7.png b/algorithms/intervals/interval_intersection/images/solutions/interval_intersection_solution_7.png new file mode 100644 index 00000000..38aab351 Binary files /dev/null and b/algorithms/intervals/interval_intersection/images/solutions/interval_intersection_solution_7.png differ diff --git a/algorithms/intervals/interval_intersection/images/solutions/interval_intersection_solution_8.png b/algorithms/intervals/interval_intersection/images/solutions/interval_intersection_solution_8.png new file mode 100644 index 00000000..93abd11e Binary files /dev/null and b/algorithms/intervals/interval_intersection/images/solutions/interval_intersection_solution_8.png differ diff --git a/algorithms/intervals/interval_intersection/test_intervals_intersection.py b/algorithms/intervals/interval_intersection/test_intervals_intersection.py new file mode 100644 index 00000000..b2b5a42f --- /dev/null +++ b/algorithms/intervals/interval_intersection/test_intervals_intersection.py @@ -0,0 +1,61 @@ +import unittest +from typing import List +from parameterized import parameterized +from algorithms.intervals.interval_intersection import intervals_intersection + +test_cases = [ + ( + [[1, 4], [5, 6], [7, 8], [9, 15]], + [[2, 4], [5, 7], [9, 15]], + [[2, 4], [5, 6], [7, 7], [9, 15]], + ), + ( + [[1, 3], [4, 6], [8, 10], [11, 15]], + [[2, 3], [10, 15]], + [[2, 3], [10, 10], [11, 15]], + ), + ( + [[1, 2], [4, 6], [7, 8], [9, 10]], + [[3, 6], [7, 8], [9, 10]], + [[4, 6], [7, 8], [9, 10]], + ), + ( + [[1, 3], [5, 6], [7, 8], [9, 10], [12, 15]], + [[2, 4], [7, 10]], + [[2, 3], [7, 8], [9, 10]], + ), + ([[1, 2]], [[1, 2]], [[1, 2]]), + ([[3, 9], [20, 31]], [[1, 8], [10, 20], [25, 37]], [[3, 8], [20, 20], [25, 31]]), + ([[5, 12], [16, 25], [28, 36]], [[0, 40]], [[5, 12], [16, 25], [28, 36]]), + ( + [[2, 9], [18, 29], [38, 48]], + [[4, 14], [20, 26], [34, 44]], + [[4, 9], [20, 26], [38, 44]], + ), + ( + [[5, 13], [25, 36]], + [[13, 25], [40, 50]], + [[13, 13], [25, 25]], + ), + ( + [[1, 12], [29, 38]], + [[16, 27], [40, 48]], + [], + ), +] + + +class IntervalsIntersectionTestCases(unittest.TestCase): + @parameterized.expand(test_cases) + def test_intervals_intersection( + self, + interval_list_a: List[List[int]], + interval_list_b: List[List[int]], + expected: List[List[int]], + ): + actual = intervals_intersection(interval_list_a, interval_list_b) + self.assertEqual(expected, actual) + + +if __name__ == "__main__": + unittest.main() diff --git a/datastructures/trees/trie/alphabet_trie/alphabet_trie.py b/datastructures/trees/trie/alphabet_trie/alphabet_trie.py index f7ddece6..3880c630 100644 --- a/datastructures/trees/trie/alphabet_trie/alphabet_trie.py +++ b/datastructures/trees/trie/alphabet_trie/alphabet_trie.py @@ -6,7 +6,7 @@ def __init__(self): self.root = AlphabetTrieNode() def insert(self, word: str) -> None: - if not word or not all('a' <= char.lower() <= 'z' for char in word): + if not word or not all("a" <= char.lower() <= "z" for char in word): raise ValueError("Word must contain only English letters (a-z)") node = self.root for char in word: