From 6c8b2bac5a85ff2c495925bc7253a71815fe2627 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 6 Jul 2026 04:51:05 +0000 Subject: [PATCH] feat: add solution for 1288. Remove Covered Intervals --- .../analysis_daily_20260706.md | 89 +++++++++++++++++++ .../1288-remove-covered-intervals/problem.md | 50 +++++++++++ .../solution_daily_20260706.go | 35 ++++++++ .../solution_daily_20260706_test.go | 56 ++++++++++++ 4 files changed, 230 insertions(+) create mode 100644 problems/1288-remove-covered-intervals/analysis_daily_20260706.md create mode 100644 problems/1288-remove-covered-intervals/problem.md create mode 100644 problems/1288-remove-covered-intervals/solution_daily_20260706.go create mode 100644 problems/1288-remove-covered-intervals/solution_daily_20260706_test.go diff --git a/problems/1288-remove-covered-intervals/analysis_daily_20260706.md b/problems/1288-remove-covered-intervals/analysis_daily_20260706.md new file mode 100644 index 0000000..fb8f17d --- /dev/null +++ b/problems/1288-remove-covered-intervals/analysis_daily_20260706.md @@ -0,0 +1,89 @@ +# 1288. Remove Covered Intervals + +[LeetCode Link](https://leetcode.com/problems/remove-covered-intervals/) + +Difficulty: Medium +Topics: Array, Sorting +Acceptance Rate: 57.0% + +## Hints + +### Hint 1 + +An interval is "covered" only in relation to *another* interval. Comparing every +pair is O(n²) and workable given the small constraints, but there is a cleaner +angle. When a problem asks you to reason about which intervals contain which, +think about what happens if you first put them in a predictable order. Which +sorting key would let you decide "covered or not" by looking only at what you've +already seen? + +### Hint 2 + +Sort the intervals by their **start** value in ascending order. After sorting, if +interval `A` comes before interval `B`, then `A.start <= B.start`. That already +satisfies the first half of the coverage condition (`c <= a`). Now coverage +reduces to a single comparison on the **end** values. As you sweep left to right, +what single number do you need to remember to know whether the current interval is +swallowed by something earlier? + +### Hint 3 + +Track the maximum end value seen so far. As you walk the sorted list, the current +interval is covered when its end is `<= maxEnd`; otherwise it survives and you +extend `maxEnd`. The one trap: two intervals sharing the same start. If `[1,4]` +and `[1,10]` are compared, `[1,4]` is covered by `[1,10]`, but a naive start-only +sort might place `[1,4]` first and wrongly count it. Fix this with the tie-break: +when starts are equal, sort by end in **descending** order so the longer interval +is processed first and correctly "eats" the shorter one. + +## Approach + +Sorting turns an all-pairs comparison into a single linear sweep. + +1. **Sort** the intervals by start ascending. On ties (equal starts), sort by end + descending. This guarantees that for any interval we look at, every previously + seen interval has a start less than or equal to the current start, and among + equal starts the widest interval is seen first. + +2. **Sweep** through the sorted list, keeping `maxEnd`, the largest end value among + all intervals processed so far. For each interval `[l, r]`: + - If `r <= maxEnd`, then some earlier interval starts no later (guaranteed by + the sort) and ends no earlier, so `[l, r]` is covered — skip it. + - Otherwise `[l, r]` is not covered; increment the surviving count and update + `maxEnd = r`. + +3. Return the count of surviving intervals. + +**Why the tie-break matters:** Consider `[[1,4],[1,10]]`. Both start at 1. If we +processed `[1,4]` first, `maxEnd` becomes 4, then `[1,10]` has end `10 > 4` and is +counted — giving 2. But `[1,4]` is actually covered by `[1,10]`, so the answer is +1. Sorting equal starts by end descending processes `[1,10]` first (`maxEnd = 10`), +then `[1,4]` has `4 <= 10` and is correctly discarded. + +**Walkthrough on Example 1** `[[1,4],[3,6],[2,8]]`: +- Sorted (start asc, end desc): `[[1,4],[2,8],[3,6]]`. +- `[1,4]`: `4 > 0`, survives. count = 1, maxEnd = 4. +- `[2,8]`: `8 > 4`, survives. count = 2, maxEnd = 8. +- `[3,6]`: `6 <= 8`, covered. count stays 2. +- Result: **2**. ✅ + +## Complexity Analysis + +Time Complexity: O(n log n) — dominated by the sort; the sweep is O(n). +Space Complexity: O(1) extra (or O(log n)–O(n) depending on the sort's internal +allocation), since we sort in place and keep only a couple of scalars. + +## Edge Cases + +- **Single interval:** `[[a, b]]` can never be covered by another — the answer is + always 1. The sweep handles this naturally. +- **Equal starts:** e.g. `[[1,4],[1,10]]`. Without the end-descending tie-break the + shorter interval is miscounted. This is the single most important case to get + right. +- **Identical/nested chains:** e.g. `[[1,2],[1,4],[1,6],[1,8]]` all share a start; + only the widest survives, so the answer is 1. +- **No coverage at all:** disjoint or partially overlapping intervals like + `[[1,2],[3,4],[5,6]]` — none is covered, so every interval counts. +- **Interval that shares an end but has a later start:** e.g. `[[2,8],[3,8]]`; + `[3,8]` is covered because `2 <= 3` and `8 <= 8`. The `<=` comparison (not `<`) + is essential here. diff --git a/problems/1288-remove-covered-intervals/problem.md b/problems/1288-remove-covered-intervals/problem.md new file mode 100644 index 0000000..29871aa --- /dev/null +++ b/problems/1288-remove-covered-intervals/problem.md @@ -0,0 +1,50 @@ +--- +number: "1288" +frontend_id: "1288" +title: "Remove Covered Intervals" +slug: "remove-covered-intervals" +difficulty: "Medium" +topics: + - "Array" + - "Sorting" +acceptance_rate: 5696.8 +is_premium: false +created_at: "2026-07-06T04:49:33.821768+00:00" +fetched_at: "2026-07-06T04:49:33.821768+00:00" +link: "https://leetcode.com/problems/remove-covered-intervals/" +date: "2026-07-06" +--- + +# 1288. Remove Covered Intervals + +Given an array `intervals` where `intervals[i] = [li, ri]` represent the interval `[li, ri)`, remove all intervals that are covered by another interval in the list. + +The interval `[a, b)` is covered by the interval `[c, d)` if and only if `c <= a` and `b <= d`. + +Return _the number of remaining intervals_. + + + +**Example 1:** + + + **Input:** intervals = [[1,4],[3,6],[2,8]] + **Output:** 2 + **Explanation:** Interval [3,6] is covered by [2,8], therefore it is removed. + + +**Example 2:** + + + **Input:** intervals = [[1,4],[2,3]] + **Output:** 1 + + + + +**Constraints:** + + * `1 <= intervals.length <= 1000` + * `intervals[i].length == 2` + * `0 <= li < ri <= 105` + * All the given intervals are **unique**. diff --git a/problems/1288-remove-covered-intervals/solution_daily_20260706.go b/problems/1288-remove-covered-intervals/solution_daily_20260706.go new file mode 100644 index 0000000..cb51c71 --- /dev/null +++ b/problems/1288-remove-covered-intervals/solution_daily_20260706.go @@ -0,0 +1,35 @@ +package main + +import "sort" + +// removeCoveredIntervals counts how many intervals remain after removing every +// interval that is fully covered by another. +// +// Approach: sort by start ascending, and on ties by end descending. After this +// sort, any earlier interval has a start <= the current start, so coverage +// depends only on the end value. Sweeping left to right while tracking the +// largest end seen (maxEnd), an interval is covered exactly when its end is +// <= maxEnd; otherwise it survives and extends maxEnd. +// +// Time: O(n log n) for the sort. Space: O(1) extra. +func removeCoveredIntervals(intervals [][]int) int { + sort.Slice(intervals, func(i, j int) bool { + if intervals[i][0] != intervals[j][0] { + return intervals[i][0] < intervals[j][0] + } + // Equal starts: put the wider interval (larger end) first so it can + // cover the narrower ones that follow. + return intervals[i][1] > intervals[j][1] + }) + + count := 0 + maxEnd := 0 + for _, iv := range intervals { + if iv[1] > maxEnd { + // Not covered by anything seen so far. + count++ + maxEnd = iv[1] + } + } + return count +} diff --git a/problems/1288-remove-covered-intervals/solution_daily_20260706_test.go b/problems/1288-remove-covered-intervals/solution_daily_20260706_test.go new file mode 100644 index 0000000..6d88f63 --- /dev/null +++ b/problems/1288-remove-covered-intervals/solution_daily_20260706_test.go @@ -0,0 +1,56 @@ +package main + +import "testing" + +func TestRemoveCoveredIntervals(t *testing.T) { + tests := []struct { + name string + intervals [][]int + expected int + }{ + { + name: "example 1: [3,6] covered by [2,8]", + intervals: [][]int{{1, 4}, {3, 6}, {2, 8}}, + expected: 2, + }, + { + name: "example 2: [2,3] covered by [1,4]", + intervals: [][]int{{1, 4}, {2, 3}}, + expected: 1, + }, + { + name: "edge case: single interval is never covered", + intervals: [][]int{{5, 9}}, + expected: 1, + }, + { + name: "edge case: equal starts, shorter is covered", + intervals: [][]int{{1, 4}, {1, 10}}, + expected: 1, + }, + { + name: "edge case: nested chain sharing a start", + intervals: [][]int{{1, 2}, {1, 4}, {1, 6}, {1, 8}}, + expected: 1, + }, + { + name: "edge case: disjoint intervals, none covered", + intervals: [][]int{{1, 2}, {3, 4}, {5, 6}}, + expected: 3, + }, + { + name: "edge case: shared end with later start is covered", + intervals: [][]int{{2, 8}, {3, 8}}, + expected: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := removeCoveredIntervals(tt.intervals) + if result != tt.expected { + t.Errorf("removeCoveredIntervals(%v) = %d, want %d", tt.intervals, result, tt.expected) + } + }) + } +}