diff --git a/DIRECTORY.md b/DIRECTORY.md index 8e60ed46..85fb2f79 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -137,6 +137,8 @@ * [Test Car Pooling](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/car_pooling/test_car_pooling.py) * Count Days * [Test Count Days Without Meetings](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/count_days/test_count_days_without_meetings.py) + * Count Non Overlapping Intervals + * [Test Count Min Non Overlapping Intervals](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/count_non_overlapping_intervals/test_count_min_non_overlapping_intervals.py) * Data Stream * [Test Data Stream As Disjoint Intervals](https://github.com/BrianLusina/PythonSnips/blob/master/algorithms/intervals/data_stream/test_data_stream_as_disjoint_intervals.py) * Employee Free Time diff --git a/algorithms/intervals/count_non_overlapping_intervals/README.md b/algorithms/intervals/count_non_overlapping_intervals/README.md new file mode 100644 index 00000000..2a30c353 --- /dev/null +++ b/algorithms/intervals/count_non_overlapping_intervals/README.md @@ -0,0 +1,61 @@ +# Non-Overlapping Intervals + +Write a function to return the minimum number of intervals that must be removed from a given array intervals, where +intervals[i] consists of a starting point starti and an ending point endi, to ensure that the remaining intervals do not +overlap. + +Example: + +```text +intervals = [[1,3],[5,8],[4,10],[11,13]] +1 + +Explanation: Removing the interval [4,10] leaves all other intervals non-overlapping. +``` + +## Solution + +This question reduces to finding the maximum number of non-overlapping intervals. Once we know that value, then we can +subtract it from the total number of intervals to get the minimum number of intervals that need to be removed. + +![Example 1](./images/examples/count_non_overlapping_intervals_example_1.png) + +To find the maximum number of non-overlapping intervals, we can sort the intervals by their end time. We then use a +greedy approach: we iterate over each sorted interval, and repeatedly try to add that interval to the set of +non-overlapping intervals. Sorting by the end time allows us to choose the intervals that end the earliest first, which +frees up more time for intervals to be included later. +We start by keeping track of a variable end which represents the end time of the latest interval in our set of +non-overlapping intervals, as well as a variable count which represents the number of non-overlapping intervals we have +found so far. + +![Solution 1](images/solutions/count_non_overlapping_intervals_solution_1.png) +![Solution 2](images/solutions/count_non_overlapping_intervals_solution_2.png) + +We then iterate over each interval starting from the second interval in the list (the first interval is always +non-overlapping). For each interval, we compare the start time of the interval to end. If it is less than end, then we +cannot add the interval to our set of non-overlapping intervals, so we move onto the next interval without updating end or +count. + +![Solution 3](./images/solutions/count_non_overlapping_intervals_solution_3.png) +![Solution 4](./images/solutions/count_non_overlapping_intervals_solution_4.png) + +If it is greater than or equal to end, then we can add the interval to our set of non-overlapping intervals by updating +count. We then update the value of end to be the end time of the current interval. + +![Solution 5](./images/solutions/count_non_overlapping_intervals_solution_5.png) +![Solution 6](./images/solutions/count_non_overlapping_intervals_solution_6.png) +![Solution 7](./images/solutions/count_non_overlapping_intervals_solution_7.png) +![Solution 8](./images/solutions/count_non_overlapping_intervals_solution_8.png) +![Solution 9](./images/solutions/count_non_overlapping_intervals_solution_9.png) + + +### 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 + +We only initialize two extra variables regardless of the input size. + diff --git a/algorithms/intervals/count_non_overlapping_intervals/__init__.py b/algorithms/intervals/count_non_overlapping_intervals/__init__.py new file mode 100644 index 00000000..07751938 --- /dev/null +++ b/algorithms/intervals/count_non_overlapping_intervals/__init__.py @@ -0,0 +1,50 @@ +from typing import List + + +def count_min_non_overlapping_intervals(intervals: List[List[int]]) -> int: + """ + Counts the minimum number of intervals that must be removed to make the intervals non-overlapping. Modifies the + input list in place + Args: + intervals (List[List[int]]): The intervals to check + Returns: + int: number of intervals to remove + """ + if not intervals: + return 0 + intervals.sort(key=lambda x: x[1]) + end = intervals[0][1] + count = 1 + for i in range(1, len(intervals)): + # Non-overlapping interval found + if intervals[i][0] >= end: + end = intervals[i][1] + count += 1 + return len(intervals) - count + + +def count_min_non_overlapping_intervals_2(intervals: List[List[int]]) -> int: + """ + Counts the minimum number of intervals that must be removed to make the intervals non-overlapping. Does not modify + the input list in place, this instead uses a sorted copy of the input intervals + Args: + intervals (List[List[int]]): The intervals to check + Returns: + int: number of intervals to remove + """ + if not intervals: + return 0 + + sorted_intervals = sorted(intervals, key=lambda x: x[1]) + + end = sorted_intervals[0][1] + count = 1 + + for current_interval in sorted_intervals[1:]: + current_interval_start, current_interval_end = current_interval + # Non-overlapping interval found + if current_interval_start >= end: + end = current_interval_end + count += 1 + + return len(sorted_intervals) - count diff --git a/algorithms/intervals/count_non_overlapping_intervals/images/examples/count_non_overlapping_intervals_example_1.png b/algorithms/intervals/count_non_overlapping_intervals/images/examples/count_non_overlapping_intervals_example_1.png new file mode 100644 index 00000000..9a81e7fd Binary files /dev/null and b/algorithms/intervals/count_non_overlapping_intervals/images/examples/count_non_overlapping_intervals_example_1.png differ diff --git a/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_1.png b/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_1.png new file mode 100644 index 00000000..1f767446 Binary files /dev/null and b/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_1.png differ diff --git a/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_2.png b/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_2.png new file mode 100644 index 00000000..d643c309 Binary files /dev/null and b/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_2.png differ diff --git a/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_3.png b/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_3.png new file mode 100644 index 00000000..6193e718 Binary files /dev/null and b/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_3.png differ diff --git a/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_4.png b/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_4.png new file mode 100644 index 00000000..519e5e04 Binary files /dev/null and b/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_4.png differ diff --git a/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_5.png b/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_5.png new file mode 100644 index 00000000..4077211a Binary files /dev/null and b/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_5.png differ diff --git a/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_6.png b/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_6.png new file mode 100644 index 00000000..156c0884 Binary files /dev/null and b/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_6.png differ diff --git a/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_7.png b/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_7.png new file mode 100644 index 00000000..2c258cff Binary files /dev/null and b/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_7.png differ diff --git a/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_8.png b/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_8.png new file mode 100644 index 00000000..feb362c8 Binary files /dev/null and b/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_8.png differ diff --git a/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_9.png b/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_9.png new file mode 100644 index 00000000..7f63526c Binary files /dev/null and b/algorithms/intervals/count_non_overlapping_intervals/images/solutions/count_non_overlapping_intervals_solution_9.png differ diff --git a/algorithms/intervals/count_non_overlapping_intervals/test_count_min_non_overlapping_intervals.py b/algorithms/intervals/count_non_overlapping_intervals/test_count_min_non_overlapping_intervals.py new file mode 100644 index 00000000..2d9563fa --- /dev/null +++ b/algorithms/intervals/count_non_overlapping_intervals/test_count_min_non_overlapping_intervals.py @@ -0,0 +1,40 @@ +import unittest +from typing import List +from copy import deepcopy +from parameterized import parameterized +from algorithms.intervals.count_non_overlapping_intervals import ( + count_min_non_overlapping_intervals, + count_min_non_overlapping_intervals_2, +) + +COUNT_NON_OVERLAPPING_INTERVALS_TEST_CASES = [ + ([[1, 3], [5, 8], [4, 10], [11, 13]], 1), + ([[1, 2], [2, 3], [3, 4], [1, 3]], 1), + ([[1, 2], [1, 2], [1, 2]], 2), + ([[1, 2], [2, 3]], 0), + ([[1, 5], [2, 3], [3, 4], [4, 6]], 1), + ([[1, 3], [3, 5], [4, 6], [5, 7]], 1), + ([[1, 3], [2, 4], [3, 5]], 1), + ([[0, 2], [1, 3], [2, 4], [3, 5], [4, 6]], 2), +] + + +class CountMinNonOverlappingIntervalsTestCase(unittest.TestCase): + @parameterized.expand(COUNT_NON_OVERLAPPING_INTERVALS_TEST_CASES) + def test_count_non_overlapping_intervals_1( + self, intervals: List[List[int]], expected: int + ): + input_intervals = deepcopy(intervals) + actual = count_min_non_overlapping_intervals(input_intervals) + self.assertEqual(expected, actual) + + @parameterized.expand(COUNT_NON_OVERLAPPING_INTERVALS_TEST_CASES) + def test_count_non_overlapping_intervals_2( + self, intervals: List[List[int]], expected: int + ): + actual = count_min_non_overlapping_intervals_2(intervals) + self.assertEqual(expected, actual) + + +if __name__ == "__main__": + unittest.main() diff --git a/algorithms/intervals/insert_interval/README.md b/algorithms/intervals/insert_interval/README.md index c6e3e5b0..2694b935 100644 --- a/algorithms/intervals/insert_interval/README.md +++ b/algorithms/intervals/insert_interval/README.md @@ -22,3 +22,54 @@ Return the updated list of intervals. ![Example 1](./images/examples/insert_interval_example_1.png) ![Example 2](./images/examples/insert_interval_example_2.png) + +## Solution + +We first want to create a new list merged to store the merged intervals we will return at the end. + +This solution operates in 3 phases: +1. Add all the intervals ending before newInterval starts to merged. +2. Merge all overlapping intervals with newInterval and add that merged interval to merged. +3. Add all the intervals starting after newInterval to merged. + +### Phase 1 + +In this phase, we add all the intervals that end before newInterval starts to merged. This involves iterating through the +intervals list until the current interval no longer ends before newInterval starts (i.e. intervals[i][1] >= newInterval[0]). + +![Solution 1](./images/solutions/insert_interval_solution_1.png) +![Solution 2](./images/solutions/insert_interval_solution_2.png) + +### Phase 2 + +In this phase, we merge all the intervals that overlap with newInterval together into a single interval by updating +newInterval to be the minimum start and maximum end of all the overlapping intervals. This involves iterating through +the intervals list until the current interval starts after newInterval ends (i.e. intervals[i][0] > newInterval[1]). +When that condition is met, we add newInterval to merged and move onto phase 3. + +![Solution 3](./images/solutions/insert_interval_solution_3.png) +![Solution 4](./images/solutions/insert_interval_solution_4.png) +![Solution 5](./images/solutions/insert_interval_solution_5.png) +![Solution 6](./images/solutions/insert_interval_solution_6.png) +![Solution 7](./images/solutions/insert_interval_solution_7.png) + +### Phase 3 + +Phase 3 involves adding all the intervals starting after newInterval to merged. This involves iterating through the +intervals list until the end of the list, and adding each interval to merged. + +After completing these 3 phases, we return merged as the final result. + +![Solution 8](./images/solutions/insert_interval_solution_8.png) +![Solution 9](./images/solutions/insert_interval_solution_9.png) +![Solution 10](./images/solutions/insert_interval_solution_10.png) + +### Complexity Analysis + +#### Time Complexity + +O(n) where n is the number of intervals. We iterate through all intervals once to merge them. + +#### Space Complexity + +O(n) where n is the number of intervals. We need space for the merged output array. diff --git a/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_1.png b/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_1.png new file mode 100644 index 00000000..5e9ffa36 Binary files /dev/null and b/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_1.png differ diff --git a/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_10.png b/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_10.png new file mode 100644 index 00000000..476a4105 Binary files /dev/null and b/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_10.png differ diff --git a/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_2.png b/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_2.png new file mode 100644 index 00000000..d1d83780 Binary files /dev/null and b/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_2.png differ diff --git a/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_3.png b/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_3.png new file mode 100644 index 00000000..3b785d07 Binary files /dev/null and b/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_3.png differ diff --git a/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_4.png b/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_4.png new file mode 100644 index 00000000..52d9ff1d Binary files /dev/null and b/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_4.png differ diff --git a/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_5.png b/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_5.png new file mode 100644 index 00000000..cde4491f Binary files /dev/null and b/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_5.png differ diff --git a/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_6.png b/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_6.png new file mode 100644 index 00000000..3084148d Binary files /dev/null and b/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_6.png differ diff --git a/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_7.png b/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_7.png new file mode 100644 index 00000000..7f069d57 Binary files /dev/null and b/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_7.png differ diff --git a/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_8.png b/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_8.png new file mode 100644 index 00000000..d610f525 Binary files /dev/null and b/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_8.png differ diff --git a/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_9.png b/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_9.png new file mode 100644 index 00000000..da083229 Binary files /dev/null and b/algorithms/intervals/insert_interval/images/solutions/insert_interval_solution_9.png differ