diff --git a/DIRECTORY.md b/DIRECTORY.md index fba60963..8e60ed46 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -131,6 +131,8 @@ * [Decoding](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/huffman/decoding.py) * [Encoding](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/huffman/encoding.py) * Intervals + * Can Attend Meetings + * [Test Can Attend Meetings](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/can_attend_meetings/test_can_attend_meetings.py) * Car Pooling * [Test Car Pooling](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/car_pooling/test_car_pooling.py) * Count Days diff --git a/algorithms/intervals/can_attend_meetings/README.md b/algorithms/intervals/can_attend_meetings/README.md new file mode 100644 index 00000000..f94bf40e --- /dev/null +++ b/algorithms/intervals/can_attend_meetings/README.md @@ -0,0 +1,23 @@ +# Can Attend Meetings + +Write a function to check if a person can attend all the meetings scheduled without any time conflicts. Given an array +intervals, where each element [s1, e1] represents a meeting starting at time s1 and ending at time e1, determine if +there are any overlapping meetings. If there is no overlap between any meetings, return true; otherwise, return false. + +Note that meetings ending and starting at the same time, such as (0,5) and (5,10), do not conflict. + +Examples: + +```text +Input: intervals = [(1,5),(3,9),(6,8)] +Output: False + +Explanation: The meetings (1,5) and (3,9) overlap. +``` + +```text +Input: intervals = [(10,12),(6,9),(13,15)] +Output: True + +Explanation: There are no overlapping meetings, so the person can attend all. +``` diff --git a/algorithms/intervals/can_attend_meetings/__init__.py b/algorithms/intervals/can_attend_meetings/__init__.py new file mode 100644 index 00000000..79682c99 --- /dev/null +++ b/algorithms/intervals/can_attend_meetings/__init__.py @@ -0,0 +1,60 @@ +from typing import List + + +def can_attend_meetings(intervals: List[List[int]]) -> bool: + """ + Checks if an employee can attend all meetings given a list of intervals representing the start and end times of each + meeting. + + A person can attend all meetings if and only if none of the meetings overlap. By sorting the intervals by start time, + we can easily check if any two consecutive intervals overlap. + + We iterate over each interval, beginning with the second interval in the sorted list. We compare the start time of + the current interval with the end time of the previous interval. If the start time of the current interval is less + than the end time of the previous interval, then the two intervals overlap and the person cannot attend both meetings, + so we return false. + + Otherwise, the person can attend both meetings, and we continue to the next interval. If we reach the end of the + list without finding any overlapping intervals, then the person can attend all meetings, and we return true. + + Complexity Analysis + + Time Complexity: O(n * logn) where n is the number of intervals. The time complexity is dominated by the sorting step. + + Space Complexity: + + Since we are sorting the intervals and creating a new sorted_intervals variable that has the sorted intervals by time + the space incurred is O(n). However, if sorting in place, then the space cost becomes O(1) and in that case no extra + extra space would be used beyond a few variables. + + Args: + intervals(list): list of intervals where each entry is a list containing the start and end time of a meeting + Returns: + bool: True if an employee can attend all meetings, false otherwise + """ + if len(intervals) == 0: + return True + + # Sort the intervals by start time first to enable easier iteration through the intervals. Meetings with similar + # start times will be close to each other, allowing quick and early exit if they overlap. + sorted_intervals = sorted(intervals, key=lambda x: x[0]) + + # Keep track of the last seen end time. + # We initialize the first interval's end time to keep track of the last interval's end that we have seen so far + last_end_time = sorted_intervals[0][1] + + # We then iterate through the list checking if there is any overlaps + # Start from the second interval in the list to check if it overlaps with the previous interval. + for current_interval in sorted_intervals[1:]: + # Get the start and end of the current interval + current_start, current_end = current_interval + + if current_start < last_end_time: + # there is an overlap, we return here + return False + + # Otherwise, we update the last end time we have seen with this interval's end time + last_end_time = current_end + + # If no overlap is found, we return True + return True diff --git a/algorithms/intervals/can_attend_meetings/test_can_attend_meetings.py b/algorithms/intervals/can_attend_meetings/test_can_attend_meetings.py new file mode 100644 index 00000000..d42f38f9 --- /dev/null +++ b/algorithms/intervals/can_attend_meetings/test_can_attend_meetings.py @@ -0,0 +1,29 @@ +import unittest +from typing import List +from parameterized import parameterized +from algorithms.intervals.can_attend_meetings import can_attend_meetings + +CAN_ATTEND_MEETINGS_TEST_CASES = [ + ([[1, 5], [3, 9], [6, 8]], False), + ([[10, 12], [6, 9], [13, 15]], True), + ([[0, 30], [5, 10], [15, 20]], False), + ([[7, 10], [2, 4]], True), + ([[1, 2], [2, 3], [3, 4]], True), + ([[1, 2], [2, 3], [3, 4]], True), + ([[1, 3], [2, 4], [4, 6]], False), + ([[0, 1], [3, 5], [6, 7]], True), + ([[10, 20], [20, 30], [30, 40]], True), + ([[1, 5], [6, 10], [11, 15]], True), + ([[5, 10], [15, 20], [10, 15]], True), +] + + +class CanAttendMeetingsTestCase(unittest.TestCase): + @parameterized.expand(CAN_ATTEND_MEETINGS_TEST_CASES) + def test_can_attend_meetings(self, intervals: List[List[int]], expected: bool): + actual = can_attend_meetings(intervals) + self.assertEqual(expected, actual) + + +if __name__ == "__main__": + unittest.main()