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 @@ -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
Expand Down
61 changes: 61 additions & 0 deletions algorithms/intervals/count_non_overlapping_intervals/README.md
Original file line number Diff line number Diff line change
@@ -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.

Original file line number Diff line number Diff line change
@@ -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
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,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()
51 changes: 51 additions & 0 deletions algorithms/intervals/insert_interval/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
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