Given an array of intervals intervals where intervals[i] = [starti, endi], return the minimum number of intervals you need to remove to make the rest of the intervals non-overlapping.
Note: You may assume the interval's end point is always bigger than its start point. Intervals [1,2] and [2,3] have borders "touching" but they don't overlap each other.
Input: intervals = [[1,2],[2,3],[3,4],[1,3]]
Output: 1
Explanation: [1,3] can be removed and the rest of the intervals are non-overlapping.
Input: intervals = [[1,2],[1,2],[1,2]]
Output: 2
Explanation: You need to remove two [1,2] to make the rest of the intervals non-overlapping.
Input: intervals = [[1,2],[2,3]]
Output: 0
Explanation: You don't need to remove any of the intervals since they're already non-overlapping.
1 <= intervals.length <= 10^5intervals[i].length == 2-5 * 10^4 <= starti < endi <= 5 * 10^4This is a classic greedy algorithm problem, also known as the Activity Selection Problem or Interval Scheduling Maximization.
Sort by end time and greedily keep intervals that end earliest!
The intuition is: if we keep intervals that end early, we leave more room for future intervals. This minimizes the number of intervals we need to remove.
Consider intervals [[1,100], [11,22], [1,11], [2,12]]:
[1,100] which blocks many other intervals[1,11], then [11,22], maximizing the count!Sorting by end time ensures we make locally optimal choices that lead to a globally optimal solution.
prevEnd = first interval's end timeremoved = 0 (count of removed intervals)[start, end]:
start >= prevEnd: No overlap, keep it! Update prevEnd = endstart < prevEnd: Overlap detected, remove this interval, increment removedremovedAt each step, we make a greedy choice: "Keep the interval with the earliest end time among all valid options."
This is optimal because:
For intervals = [[1,2], [2,3], [3,4], [1,3]]:
[[1,2], [1,3], [2,3], [3,4]][1,2], set prevEnd = 2, removed = 0[1,3]: 1 < 2 → Overlap! Remove it, removed = 1[2,3]: 2 >= 2 → No overlap, keep it, prevEnd = 3[3,4]: 3 >= 3 → No overlap, keep it, prevEnd = 4removed = 1Time Complexity: O(n log n)
Space Complexity: O(1) or O(log n)
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
if (intervals.length == 0) return 0;
// Sort by end time (greedy choice)
Arrays.sort(intervals, (a, b) -> Integer.compare(a[1], b[1]));
int prevEnd = intervals[0][1];
int removed = 0;
// Check each interval for overlap
for (int i = 1; i < intervals.length; i++) {
int start = intervals[i][0];
int end = intervals[i][1];
if (start < prevEnd) {
// Overlap detected - remove this interval
removed++;
} else {
// No overlap - keep this interval
prevEnd = end;
}
}
return removed;
}
}
def eraseOverlapIntervals(intervals: List[List[int]]) -> int:
if not intervals:
return 0
# Sort by end time (greedy choice)
intervals.sort(key=lambda x: x[1])
prev_end = intervals[0][1]
removed = 0
# Check each interval for overlap
for i in range(1, len(intervals)):
start, end = intervals[i]
if start < prev_end:
# Overlap detected - remove this interval
removed += 1
else:
# No overlap - keep this interval
prev_end = end
return removed
You can also sort by start time, but the logic is more complex:
public int eraseOverlapIntervals(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
int prevEnd = intervals[0][1];
int removed = 0;
for (int i = 1; i < intervals.length; i++) {
if (intervals[i][0] < prevEnd) {
// Overlap - remove the one with later end time
removed++;
prevEnd = Math.min(prevEnd, intervals[i][1]);
} else {
prevEnd = intervals[i][1];
}
}
return removed;
}
Note: Sorting by end time is simpler and more intuitive!
This problem demonstrates:
1import java.util.*;
2
3class Solution {
4 // Solution 1: Greedy (Sort by End Time)
5 public int eraseOverlapIntervals(int[][] intervals) {
6 if (intervals.length == 0) return 0;
7
8 // Sort by end time
9 Arrays.sort(intervals, (a, b) -> Integer.compare(a[1], b[1]));
10
11 int end = intervals[0][1];
12 int removeCount = 0;
13
14 for (int i = 1; i < intervals.length; i++) {
15 // If current interval starts before previous ends (overlap)
16 if (intervals[i][0] < end) {
17 removeCount++;
18 } else {
19 // No overlap, update end
20 end = intervals[i][1];
21 }
22 }
23
24 return removeCount;
25 }
26
27 // Solution 2: Greedy (Alternative)
28 public int eraseOverlapIntervalsAlt(int[][] intervals) {
29 if (intervals.length == 0) return 0;
30
31 Arrays.sort(intervals, (a, b) -> a[1] - b[1]);
32
33 int count = 1; // First interval is always included
34 int end = intervals[0][1];
35
36 for (int i = 1; i < intervals.length; i++) {
37 if (intervals[i][0] >= end) {
38 // Non-overlapping
39 count++;
40 end = intervals[i][1];
41 }
42 }
43
44 return intervals.length - count;
45 }
46
47 // Solution 3: Greedy (Sort by Start, then End)
48 public int eraseOverlapIntervalsStart(int[][] intervals) {
49 if (intervals.length == 0) return 0;
50
51 // Sort by start time, then by end time
52 Arrays.sort(intervals, (a, b) -> {
53 if (a[0] != b[0]) return a[0] - b[0];
54 return a[1] - b[1];
55 });
56
57 int prevEnd = intervals[0][1];
58 int removeCount = 0;
59
60 for (int i = 1; i < intervals.length; i++) {
61 int start = intervals[i][0];
62 int end = intervals[i][1];
63
64 if (start < prevEnd) {
65 // Overlap: remove the one with later end
66 removeCount++;
67 prevEnd = Math.min(prevEnd, end);
68 } else {
69 // No overlap
70 prevEnd = end;
71 }
72 }
73
74 return removeCount;
75 }
76}
77