Skip to content

Commit 97d2f2b

Browse files
committed
feat(algorithms, intervals): remove covered intervals
1 parent dbac2b4 commit 97d2f2b

15 files changed

+213
-0
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Remove Covered Intervals
2+
3+
Given an array of intervals, where each interval is represented as intervals[i]=[li,ri) (indicating the range from
4+
li to ri, inclusive of li and exclusive of ri), remove all intervals that are completely covered by another interval in
5+
the list. Return the count of intervals that remain after removing the covered ones.
6+
7+
> Note An interval [a, b) is considered covered by another interval [c,d) if and only if c ⇐ a and b ⇐ d.
8+
9+
## Constraints
10+
11+
- 1 <= intervals.length <= 10^4
12+
- intervals[i].length == 2
13+
- 0 <= li < ri <= 10^5
14+
- All the give intervals are unique
15+
16+
## Examples
17+
18+
![Example 1](./images/examples/remove_covered_intervals_example_1.png)
19+
![Example 2](./images/examples/remove_covered_intervals_example_2.png)
20+
![Example 3](./images/examples/remove_covered_intervals_example_3.png)
21+
![Example 4](./images/examples/remove_covered_intervals_example_4.png)
22+
![Example 5](./images/examples/remove_covered_intervals_example_5.png)
23+
24+
25+
## Solution
26+
27+
The first step is to simplify the process by sorting the intervals. Sorting by the start point in ascending order is
28+
straightforward and simplifies the iteration process. However, an important edge case arises when two intervals share
29+
the same start point. In such scenarios, sorting solely by the start point would fail to correctly identify covered
30+
intervals. To handle this, we sort intervals with the same start point by their endpoint in descending order, ensuring
31+
that longer intervals come first. This sorting strategy guarantees that if one interval covers another, it will be
32+
positioned earlier in the sorted list. Once the intervals are sorted, we iterate through them while keeping track of the
33+
maximum endpoint seen so far. If the current interval’s end point exceeds this maximum, it is not covered, so we increment
34+
the count and update the maximum end. The interval is covered and skipped if the endpoint is less than or equal to the
35+
maximum. After completing the iteration, the final count reflects the remaining non-covered intervals.
36+
37+
Now, let’s look at the solution steps below:
38+
39+
1. If the start points are the same, sort the intervals by the start point in ascending order, otherwise, sort by the
40+
endpoint in descending order to prioritize longer intervals.
41+
2. Initialize the count with zero to track the remaining (non-covered) intervals.
42+
3. Initialize prev_end with zero to track the maximum end value we’ve seen..
43+
4. Start iterating through intervals for each interval [start, end] in the sorted list:
44+
- If end > prev_end, any previous interval does not cover the interval.
45+
- Increment count by 1
46+
- Update prev_end to end.
47+
- Else:
48+
- A previous interval covers the interval, so we skip it.
49+
50+
5. After iterating through all the intervals, the return count is the final value, representing the remaining intervals.
51+
52+
Let’s look at the following illustration to get a better understanding of the solution:
53+
54+
![Solution 1](./images/solutions/remove_covered_intervals_solution_1.png)
55+
![Solution 2](./images/solutions/remove_covered_intervals_solution_2.png)
56+
![Solution 3](./images/solutions/remove_covered_intervals_solution_3.png)
57+
![Solution 4](./images/solutions/remove_covered_intervals_solution_4.png)
58+
![Solution 5](./images/solutions/remove_covered_intervals_solution_5.png)
59+
![Solution 6](./images/solutions/remove_covered_intervals_solution_6.png)
60+
![Solution 7](./images/solutions/remove_covered_intervals_solution_7.png)
61+
62+
### Time Complexity
63+
64+
The time complexity of the solution is O(n logn), where n is the number of intervals. This is because sorting the
65+
intervals takes O(n logn) time, and the subsequent iteration through the intervals takes O(n) time. Therefore, the
66+
overall time complexity is dominated by the sorting step, resulting in O(n logn).
67+
68+
### Space Complexity
69+
70+
The sorting operation has a space complexity of O(n) in the worst case due to additional memory required for temporary
71+
arrays during the sorting process. Apart from the space used by the built-in sorting algorithm, the algorithm’s space
72+
complexity is constant, O(1). Therefore, the overall space complexity of the solution is O(n).
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from typing import List
2+
3+
4+
def remove_covered_intervals(intervals: List[List[int]]) -> int:
5+
"""
6+
Finds the number of intervals that are not covered by any other interval in the list.
7+
8+
This uses a greedy approach to find the number of intervals that are not covered by any other interval in the list.
9+
Note An interval [a, b) is considered covered by another interval [c,d) if and only if c ⇐ a and b ⇐ d.
10+
11+
So, the timeline will look something like this:
12+
c---a---b---d
13+
14+
If b <= d, then [a, b) is covered by [c, d)
15+
16+
Args:
17+
intervals (List[List[int]]): A list of intervals, where each interval is represented as [start, end]
18+
Returns:
19+
int: The number of intervals that are not covered by any other interval in the list
20+
"""
21+
# early return if there are no intervals to begin with
22+
if not intervals:
23+
return 0
24+
25+
# Sort intervals by start time in ascending order and then by end time in descending order. We sort by the end time
26+
# to remove a tie-breaker where two intervals have the same start time.
27+
# This will incur a time complexity of O(nlogn). We sort in place, so, we do not
28+
# incur any additional space complexity by copying over to a new list. The assumption made here is that it is okay
29+
# to mutate the input list.
30+
intervals.sort(key=lambda x: (x[0], -x[1]))
31+
32+
# keep track of the last max end seen so far, we use a large negative infinity to cover all possible numbers
33+
max_end_seen = float('-inf')
34+
count = 0
35+
36+
# We then iterate through the given intervals
37+
for _, current_end in intervals:
38+
if current_end > max_end_seen:
39+
count += 1
40+
max_end_seen = current_end
41+
42+
# return the count of non-overlapping intervals
43+
return count
91.5 KB
Loading
98.6 KB
Loading
67 KB
Loading
77.5 KB
Loading
76 KB
Loading
47 KB
Loading
76.4 KB
Loading
76.6 KB
Loading

0 commit comments

Comments
 (0)