Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
118 changes: 118 additions & 0 deletions algorithms/intervals/interval_intersection/README.md
Original file line number Diff line number Diff line change
@@ -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.
60 changes: 60 additions & 0 deletions algorithms/intervals/interval_intersection/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -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()
2 changes: 1 addition & 1 deletion datastructures/trees/trie/alphabet_trie/alphabet_trie.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading