1272. Remove Interval 🔒
Problem Description
You are given a sorted list of disjoint intervals where each interval is represented as [a, b)
, meaning it includes a
but excludes b
. These intervals represent a set of real numbers - a number x
belongs to the set if a <= x < b
for at least one interval.
You need to remove another interval toBeRemoved
from this set and return the resulting intervals.
Key Points:
- The input intervals are disjoint (non-overlapping) and sorted
- Each interval uses half-open notation
[a, b)
wherea
is included butb
is excluded - You must remove all numbers that fall within
toBeRemoved
from the original intervals - The output should also be a sorted list of disjoint intervals
Example scenarios when removing an interval:
- If an original interval doesn't overlap with
toBeRemoved
, it remains unchanged - If an original interval is completely inside
toBeRemoved
, it gets removed entirely - If an original interval partially overlaps with
toBeRemoved
, it gets split into one or two smaller intervals
The solution iterates through each interval [a, b)
and compares it with toBeRemoved = [x, y)
:
- If
a >= y
orb <= x
: No overlap, keep the interval as is - If
a < x
andb > x
: The interval extends beforetoBeRemoved
, so keep the part[a, x)
- If
a < y
andb > y
: The interval extends aftertoBeRemoved
, so keep the part[y, b)
- If the interval is completely covered by
toBeRemoved
, it's removed (not added to the result)
Intuition
When we need to remove an interval from a set of intervals, we can think of it like cutting out a section from multiple line segments. For each original interval, we need to determine how it relates to the "cut" we're making.
The key insight is that for any interval [a, b)
and the removal interval [x, y)
, there are only a few possible relationships:
-
No overlap: The intervals don't touch at all. This happens when the original interval ends before the removal starts (
b <= x
) or starts after the removal ends (a >= y
). In this case, we keep the original interval unchanged. -
Partial overlap: The removal "cuts" through the original interval. This creates two scenarios:
- The original interval starts before the removal (
a < x
): We keep the left portion[a, x)
- The original interval ends after the removal (
b > y
): We keep the right portion[y, b)
- If both conditions are true, we get two pieces:
[a, x)
and[y, b)
- The original interval starts before the removal (
-
Complete overlap: The removal completely covers the original interval. This means the entire interval disappears.
The beauty of this approach is that we can handle all cases in a single pass through the intervals. Since the intervals are already sorted and disjoint, we don't need to worry about merging results or maintaining order - we can simply process each interval independently and append the resulting pieces to our answer.
The condition checks in the code elegantly handle all these cases:
- First, we check if there's no overlap at all (
a >= y or b <= x
) - If there is overlap, we check which parts of the original interval survive:
- The left part survives if
a < x
- The right part survives if
b > y
- The left part survives if
This approach naturally handles the case where an interval is completely consumed (neither condition for surviving parts is met) by simply not adding anything to the result.
Solution Approach
The implementation follows a straightforward case-by-case analysis as described in the reference approach. Let's walk through the algorithm step by step:
Algorithm Steps:
-
Extract the removal interval boundaries: We first extract
x
andy
fromtoBeRemoved
, representing the interval[x, y)
to be removed. -
Initialize result list: Create an empty list
ans
to store the resulting intervals after removal. -
Process each interval: For each interval
[a, b)
in the input list, we determine its relationship with[x, y)
:- Case 1: No intersection (
a >= y
orb <= x
)- The interval is completely outside the removal range
- Add the entire interval
[a, b]
to the answer unchanged
- Case 2: Intersection exists (when Case 1 is false)
- Check if the left part survives: If
a < x
, add[a, x]
to the answer - Check if the right part survives: If
b > y
, add[y, b]
to the answer - Note: Both conditions can be true simultaneously, resulting in the interval being split into two parts
- Check if the left part survives: If
- Case 1: No intersection (
Key Implementation Details:
- The algorithm processes intervals in order, maintaining the sorted property naturally
- No additional data structures are needed beyond the result list
- Time complexity:
O(n)
wheren
is the number of intervals, as we process each interval exactly once - Space complexity:
O(1)
for the algorithm itself (not counting the output array)
Why this approach works:
The solution leverages the fact that the input intervals are disjoint and sorted. This means:
- We can process each interval independently without worrying about overlaps between different input intervals
- The output will automatically be sorted since we process intervals in order
- Each interval can contribute at most 2 intervals to the output (when split by the removal)
The elegance of this solution lies in its simplicity - by handling just two main cases (no overlap vs. overlap), and within the overlap case checking which parts survive, we cover all possible scenarios without complex logic or edge case handling.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate how the solution works.
Input:
- intervals =
[[0,2), [3,4), [5,7)]
- toBeRemoved =
[1,6)
We need to remove the range [1,6)
from our intervals.
Step-by-step processing:
Interval 1: [0,2)
- Compare with toBeRemoved
[1,6)
: - Is
0 >= 6
or2 <= 1
? No, so there IS overlap - Check left part: Is
0 < 1
? Yes, so we keep[0,1)
- Check right part: Is
2 > 6
? No, so no right part survives - Result so far:
[[0,1)]
Interval 2: [3,4)
- Compare with toBeRemoved
[1,6)
: - Is
3 >= 6
or4 <= 1
? No, so there IS overlap - Check left part: Is
3 < 1
? No, so no left part survives - Check right part: Is
4 > 6
? No, so no right part survives - This interval is completely removed!
- Result so far:
[[0,1)]
Interval 3: [5,7)
- Compare with toBeRemoved
[1,6)
: - Is
5 >= 6
or7 <= 1
? No, so there IS overlap - Check left part: Is
5 < 1
? No, so no left part survives - Check right part: Is
7 > 6
? Yes, so we keep[6,7)
- Result so far:
[[0,1), [6,7)]
Final output: [[0,1), [6,7)]
Visual representation:
Original: [0====2) [3==4) [5====7) ToBeRemoved: [1==================6) Result: [0=1) [6==7)
This example demonstrates all three scenarios:
- Interval
[0,2)
partially overlaps - we keep the left portion - Interval
[3,4)
is completely covered - it's removed entirely - Interval
[5,7)
partially overlaps - we keep the right portion
Solution Implementation
1class Solution:
2 def removeInterval(
3 self, intervals: List[List[int]], toBeRemoved: List[int]
4 ) -> List[List[int]]:
5 """
6 Remove a given interval from a list of disjoint intervals.
7
8 Args:
9 intervals: List of disjoint intervals [start, end]
10 toBeRemoved: The interval to be removed [start, end]
11
12 Returns:
13 List of remaining intervals after removal
14 """
15 # Extract the start and end points of the interval to be removed
16 remove_start, remove_end = toBeRemoved
17 result = []
18
19 # Process each interval in the input list
20 for interval_start, interval_end in intervals:
21 # Case 1: Current interval doesn't overlap with the removal interval
22 # Either it's completely before or completely after
23 if interval_start >= remove_end or interval_end <= remove_start:
24 result.append([interval_start, interval_end])
25 else:
26 # Case 2: There's an overlap, need to keep the non-overlapping parts
27
28 # Keep the left part if it exists (before the removal interval starts)
29 if interval_start < remove_start:
30 result.append([interval_start, remove_start])
31
32 # Keep the right part if it exists (after the removal interval ends)
33 if interval_end > remove_end:
34 result.append([remove_end, interval_end])
35
36 return result
37
1class Solution {
2 public List<List<Integer>> removeInterval(int[][] intervals, int[] toBeRemoved) {
3 // Extract the start and end points of the interval to be removed
4 int removeStart = toBeRemoved[0];
5 int removeEnd = toBeRemoved[1];
6
7 // Result list to store the remaining intervals after removal
8 List<List<Integer>> result = new ArrayList<>();
9
10 // Process each interval in the input array
11 for (int[] interval : intervals) {
12 int intervalStart = interval[0];
13 int intervalEnd = interval[1];
14
15 // Case 1: Current interval has no overlap with the removal interval
16 // Either the interval ends before removal starts, or starts after removal ends
17 if (intervalStart >= removeEnd || intervalEnd <= removeStart) {
18 result.add(Arrays.asList(intervalStart, intervalEnd));
19 }
20 // Case 2: Current interval overlaps with the removal interval
21 else {
22 // If the interval starts before the removal interval,
23 // keep the portion before the removal starts
24 if (intervalStart < removeStart) {
25 result.add(Arrays.asList(intervalStart, removeStart));
26 }
27
28 // If the interval ends after the removal interval,
29 // keep the portion after the removal ends
30 if (intervalEnd > removeEnd) {
31 result.add(Arrays.asList(removeEnd, intervalEnd));
32 }
33 }
34 }
35
36 return result;
37 }
38}
39
1class Solution {
2public:
3 vector<vector<int>> removeInterval(vector<vector<int>>& intervals, vector<int>& toBeRemoved) {
4 // Extract the boundaries of the interval to be removed
5 int removeStart = toBeRemoved[0];
6 int removeEnd = toBeRemoved[1];
7
8 // Result vector to store the remaining intervals after removal
9 vector<vector<int>> result;
10
11 // Process each interval in the input list
12 for (auto& interval : intervals) {
13 int intervalStart = interval[0];
14 int intervalEnd = interval[1];
15
16 // Case 1: No overlap - interval is completely before or after the removal range
17 if (intervalStart >= removeEnd || intervalEnd <= removeStart) {
18 result.push_back(interval);
19 }
20 // Case 2: There is overlap - need to keep the non-overlapping portions
21 else {
22 // Keep the left portion if it exists (before the removal range)
23 if (intervalStart < removeStart) {
24 result.push_back({intervalStart, removeStart});
25 }
26
27 // Keep the right portion if it exists (after the removal range)
28 if (intervalEnd > removeEnd) {
29 result.push_back({removeEnd, intervalEnd});
30 }
31 }
32 }
33
34 return result;
35 }
36};
37
1function removeInterval(intervals: number[][], toBeRemoved: number[]): number[][] {
2 // Extract the boundaries of the interval to be removed
3 const removeStart: number = toBeRemoved[0];
4 const removeEnd: number = toBeRemoved[1];
5
6 // Result array to store the remaining intervals after removal
7 const result: number[][] = [];
8
9 // Process each interval in the input list
10 for (const interval of intervals) {
11 const intervalStart: number = interval[0];
12 const intervalEnd: number = interval[1];
13
14 // Case 1: No overlap - interval is completely before or after the removal range
15 if (intervalStart >= removeEnd || intervalEnd <= removeStart) {
16 result.push(interval);
17 }
18 // Case 2: There is overlap - need to keep the non-overlapping portions
19 else {
20 // Keep the left portion if it exists (before the removal range)
21 if (intervalStart < removeStart) {
22 result.push([intervalStart, removeStart]);
23 }
24
25 // Keep the right portion if it exists (after the removal range)
26 if (intervalEnd > removeEnd) {
27 result.push([removeEnd, intervalEnd]);
28 }
29 }
30 }
31
32 return result;
33}
34
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the intervals list. The algorithm iterates through each interval in the input list exactly once. For each interval, it performs constant-time operations: comparing boundaries with the toBeRemoved
interval and potentially appending up to two new intervals to the result list. Since we visit each interval once and perform O(1)
operations per interval, the overall time complexity is O(n)
.
The space complexity is O(1)
for auxiliary space. While the output list ans
can contain up to 2n
intervals in the worst case (when each original interval is split into two parts), this space is required for the output and is not considered auxiliary space. The only extra space used by the algorithm consists of the variables x
, y
, a
, and b
, which are constant regardless of the input size. Therefore, the auxiliary space complexity is O(1)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Confusion Between Interval Notation: [a, b)
vs [a, b]
The problem statement uses half-open interval notation [a, b)
where a
is included but b
is excluded. However, the actual implementation often uses closed intervals [a, b]
where both endpoints are included. This inconsistency can lead to off-by-one errors.
The Pitfall:
- Misunderstanding whether the intervals are inclusive or exclusive at endpoints
- Incorrectly handling boundary conditions when intervals touch but don't overlap
- For example, if using
[a, b)
notation, intervals[1, 3)
and[3, 5)
don't overlap, but with[a, b]
notation,[1, 3]
and[3, 5]
do overlap at point 3
Solution:
Be consistent with your interval representation throughout the solution. If the problem uses [a, b]
(closed intervals), ensure your comparisons reflect this:
# For closed intervals [a, b], no overlap condition becomes: if interval_start > remove_end or interval_end < remove_start: # No overlap
2. Incorrectly Handling Interval Splitting
When an interval needs to be split into two parts (the interval extends both before and after the removal interval), developers might accidentally add only one part or use wrong boundaries.
The Pitfall:
# WRONG: Using 'elif' instead of separate 'if' statements if interval_start < remove_start: result.append([interval_start, remove_start]) elif interval_end > remove_end: # This won't execute if first condition is true! result.append([remove_end, interval_end])
Solution:
Use independent if
statements to check both conditions:
# CORRECT: Both parts can be added when interval spans the removal if interval_start < remove_start: result.append([interval_start, remove_start]) if interval_end > remove_end: # Independent check result.append([remove_end, interval_end])
3. Modifying Boundaries Incorrectly During Split
When splitting intervals, using the wrong boundary values (e.g., using remove_start - 1
or remove_end + 1
unnecessarily) can create gaps or overlaps in the result.
The Pitfall:
# WRONG: Creating gaps by adjusting boundaries incorrectly if interval_start < remove_start: result.append([interval_start, remove_start - 1]) # Creates gap!
Solution: Use the exact boundary values without adjustment:
# CORRECT: Precise boundary handling if interval_start < remove_start: result.append([interval_start, remove_start]) # Exact boundary
4. Not Handling Edge Cases Properly
Failing to consider edge cases where the removal interval extends beyond all intervals or doesn't intersect with any interval at all.
The Pitfall: Assuming the removal interval will always intersect with at least one interval, or that it falls within the bounds of the existing intervals.
Solution: The algorithm naturally handles these cases:
- If
toBeRemoved
doesn't intersect with any interval, all intervals are kept unchanged - If
toBeRemoved
covers all intervals, the result will be an empty list - No special edge case handling is needed if the logic is correct
Which technique can we use to find the middle of a linked list?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!