Lesson 223 of 250
435. Non-overlapping Intervals
NeetCode: Non-overlapping Intervals · LeetCode: 435. Non-overlapping Intervals · Difficulty: Medium
Given an array of intervals [start, end], return the minimum number of intervals to remove so that the remaining ones do not overlap. Intervals that only touch at an endpoint ([1,2] and [2,3]) do not overlap.
Example
Input: intervals = [[1,2],[2,4],[1,4]] Output: 1 (remove [1,4])
Input: intervals = [[1,2],[2,4]] Output: 0Brute force: longest chain by DP
Removing the fewest intervals is the same as keeping the most. Sort by start; let dp[i] be the largest set of non-overlapping intervals that ends with interval i. Then dp[i] = 1 + max(dp[j]) over earlier j with end_j <= start_i. The answer is n - max(dp).
import java.util.Arrays;
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
int n = intervals.length;
if (n == 0) {
return 0;
}
int[][] sorted = intervals.clone();
Arrays.sort(sorted, (a, b) -> a[0] != b[0] ? Integer.compare(a[0], b[0]) : Integer.compare(a[1], b[1]));
int[] dp = new int[n];
Arrays.fill(dp, 1);
int longest = 1;
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
if (sorted[j][1] <= sorted[i][0]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
longest = Math.max(longest, dp[i]);
}
return n - longest;
}
}
public class Main {
public static void main(String[] args) {
Solution solution = new Solution();
System.out.println(solution.eraseOverlapIntervals(new int[][] {{1, 2}, {2, 4}, {1, 4}}));
System.out.println(solution.eraseOverlapIntervals(new int[][] {{1, 2}, {2, 4}}));
System.out.println(solution.eraseOverlapIntervals(new int[][] {{1, 2}, {1, 2}, {1, 2}}));
}
}1
0
2Time: O(n^2). Space: O(n).
Optimal: sort by end, keep whatever ends earliest
This is the activity-selection greedy. Sort by end. Always keep the interval that finishes first, because finishing early leaves the most room for the rest; any conflicting interval that starts before that end is removed. Keep the end of the last kept interval; an interval whose start is >= end is kept and updates the end; otherwise it overlaps and counts as one removal.
Sorting by end (not start) is what makes the choice safe: among overlapping intervals, the one with the smallest end can never block more future intervals than the others.
import java.util.Arrays;
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
if (intervals.length == 0) {
return 0;
}
int[][] sorted = intervals.clone();
Arrays.sort(sorted, (a, b) -> Integer.compare(a[1], b[1]));
int keptEnd = sorted[0][1];
int removed = 0;
for (int i = 1; i < sorted.length; i++) {
if (sorted[i][0] >= keptEnd) {
keptEnd = sorted[i][1];
} else {
removed++;
}
}
return removed;
}
}
public class Main {
public static void main(String[] args) {
Solution solution = new Solution();
System.out.println(solution.eraseOverlapIntervals(new int[][] {{1, 2}, {2, 4}, {1, 4}}));
System.out.println(solution.eraseOverlapIntervals(new int[][] {{1, 2}, {2, 4}}));
System.out.println(solution.eraseOverlapIntervals(new int[][] {{1, 2}, {1, 2}, {1, 2}}));
}
}1
0
2Time: O(n log n). Space: O(n) for the sorted copy of the outer array.
