1288. Remove Covered Intervals - Explanation

Problem Link



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Sorting - Sorting arrays with custom comparators (by start time, then by end time)
  • Interval Problems - Understanding interval containment and overlap concepts
  • Greedy Algorithms - Processing intervals in sorted order to make optimal decisions

1. Brute Force

Intuition

An interval is "covered" if another interval completely contains it. We check every pair of intervals to see if one covers the other. For interval i to be covered by interval j, we need j's start to be at or before i's start, and j's end to be at or after i's end. We count how many intervals are not covered by any other.

Algorithm

  1. Start with the total count of intervals.
  2. For each interval i, check all other intervals j.
  3. If interval j covers interval i (starts earlier or equal, ends later or equal), decrement the count and move to the next interval.
  4. Return the remaining count of uncovered intervals.

Time Complexity: O(n^2) because we check each pair of intervals.

class Solution:
    def removeCoveredIntervals(self, intervals: List[List[int]]) -> int:
        n = len(intervals)
        res = n

        for i in range(n):
            for j in range(n):
                if (i != j and intervals[j][0] <= intervals[i][0] and
                    intervals[j][1] >= intervals[i][1]
                ):
                    res -= 1
                    break

        return res

Time & Space Complexity

  • Time complexity: O(n2)O(n ^ 2)
  • Space complexity: O(1)O(1) extra space.

2. Sorting - I

Intuition

Sorting helps us process intervals in an order where we can easily detect covered intervals. By sorting by start time (ascending) and then by end time (descending), intervals that share the same start will have the longest one first. This means when we encounter a new interval, we only need to check if it's covered by the most recent "dominant" interval we've seen.

Algorithm

  1. Sort intervals by start time ascending, and by end time descending for ties.
  2. Track the previous interval's boundaries.
  3. For each interval, if it's completely contained within the previous interval's boundaries, skip it.
  4. Otherwise, count it as uncovered and update the tracked boundaries.
  5. Return the count of uncovered intervals.

This sorting strategy ensures that once we sort, we only need to track prevL and prevR.

class Solution:
    def removeCoveredIntervals(self, intervals: List[List[int]]) -> int:
        intervals.sort(key=lambda x: (x[0], -x[1]))
        res = 1
        prevL, prevR = intervals[0][0], intervals[0][1]
        for l, r in intervals:
            if prevL <= l and prevR >= r:
                continue
            res += 1
            prevL, prevR = l, r

        return res

Time & Space Complexity

  • Time complexity: O(nlogn)O(n\log n)
  • Space complexity: O(1)O(1) or O(n)O(n) depending on the sorting algorithm.

3. Sorting - II

Intuition

With a simpler sort by start time only, we track the maximum end point seen so far. An interval is covered if both its start is greater than or equal to a previous start AND its end is within the maximum end we've tracked. By keeping the maximum end updated, we efficiently determine coverage in a single pass.

Algorithm

  1. Sort intervals by start time.
  2. Track the start and maximum end of the current "dominant" interval.
  3. For each interval, check if it extends beyond the current coverage (new start strictly greater AND new end strictly greater).
  4. If so, it's a new uncovered interval. Update the tracked start and increment the count.
  5. Always update the maximum end seen.
  6. Return the count.

This approach avoids the need for special sorting by end time in reverse.

class Solution:
    def removeCoveredIntervals(self, intervals: List[List[int]]) -> int:
        intervals.sort()
        res, start, end = 1, intervals[0][0], intervals[0][1]

        for l, r in intervals:
            if start < l and end < r:
                start = l
                res += 1
            end = max(end, r)

        return res

Time & Space Complexity

  • Time complexity: O(nlogn)O(n \log n)
  • Space complexity: O(1)O(1) or O(n)O(n) depending on the sorting algorithm.

Common Pitfalls

Incorrect Sorting Order

The most common mistake is sorting only by start time without considering the end time. When two intervals share the same start, the longer one should come first (sort by end time descending). Otherwise, you might incorrectly count the longer interval as covered by the shorter one. For example, [1,4] and [1,2] with the same start: if [1,2] comes first, you'd miss that [1,2] is covered by [1,4].

Confusing Covered vs Overlapping

An interval is covered only if another interval completely contains it (start <= start AND end >= end). Some solutions incorrectly check for overlap instead of complete containment. For [1,4] to cover [2,3], we need 1 <= 2 AND 4 >= 3. Partial overlaps like [1,3] and [2,4] don't count as one covering the other.

Off-by-One in Return Value

The problem asks for the count of intervals remaining after removal, not the count of covered intervals. Some solutions return the number of covered intervals instead of subtracting from the total. If you find k covered intervals out of n total, return n - k, not k.