Непересекающиеся интервалы
Описание задачи #
Дан массив интервалов intervals, где intervals[i] = [starti, endi].
Напишите функцию, которая возвращает минимальное количество интервалов, которое вам нужно удалить, чтобы остальные интервалы не пересекались.
Ограничения #
- Длина массива находится в диапазоне от 1 до 105
- Каждый элемент массива также является массивом из двух элементов
- Значения границ интервалов находятся в диапазоне от -5 * 104 до 5 * 104
- Нижняя граница интервала всегда меньше верхней
Примеры #
- 
Входные данные: [[1,2],[2,3],[3,4],[1,3]]Ответ: 1Интервал [1,3]можно удалить, а остальные не перекрываются.
- 
Входные данные: [[1,2],[1,2],[1,2]]Ответ: 2Нужно удалить два интервала [1,2], чтобы остался один не пересекающийся.
- 
Входные данные: [[1,2],[2,3]]Ответ: 0Не нужно удалять какие-либо интервалы, поскольку они уже не перекрываются. 
Решение #
Рассмотрим два интервала с самым ранним временем окончания.
Допустим, более раннее время окончания — x, а более позднее — y, то есть х < у.
Если мы можем сохранить только один интервал, следует ли нам выбрать тот, который заканчивается на x или тот, который оканчивается на y?
Чтобы избежать дублирования, мы всегда должны жадно выбирать интервал с более ранним временем окончания x.
Логику, стоящую за этим, можно описать следующим образом:
- Мы выбираем либо x, либоy. Назовем наш выборk.
- Чтобы избежать пересечения, время начала следующего интервала, который мы выбираем, должно быть больше или равно k.
- Мы хотим максимизировать интервалы, которые мы используем (без пересечения), поэтому мы хотим максимизировать выбор для следующего интервала.
- Поскольку время начала следующего интервала должно быть больше или равно k, большее значениеkникогда не сможет дать нам больше выбора, чем меньшее значениеk.
- Таким образом, мы должны попытаться минимизировать k. Следовательно, нам всегда следует жадно выбиратьx, посколькуx < y.
В общем, k равно времени окончания самого последнего сохраненного нами интервала.
Решение задачи мы начинаем с сортировки интервалов по времени окончания, чтобы мы могли обрабатывать интервалы по порядку.
Поскольку мы отсортировали интервалы по времени окончания, y должно быть больше k.
Это дает нам два возможных варианта:
- Вариант 1, x >= k: мы можем смело использовать этот интервал, поскольку он не приведет к пересечению.
- Вариант 2, x < k: использование этого интервала приведет к пересечению. Как мы установили ранее, нам всегда следует брать интервалы с более ранним временем окончания. Посколькуy > k, мы должны удалить текущий интервал.
Реализация #
- 
package main import "sort" func eraseOverlapIntervals(intervals [][]int) int { sort.Slice(intervals, func(i, j int) bool { return intervals[i][1] < intervals[j][1] }) res := 0 k := intervals[0][1] for idx := 1; idx < len(intervals); idx++ { end := intervals[idx][1] start := intervals[idx][0] if start >= k { k = end } else { res++ } } return res }
- 
export const eraseOverlapIntervals = (intervals: number[][]): number => { intervals.sort((a, b) => { return a[1] - b[1] }) let res = 0 let k = intervals[0][1] for (let i = 1; i < intervals.length; i++) { const end = intervals[i][1] const start = intervals[i][0] if (start >= k) { k = end } else { res++ } } return res }
Оценка сложности #
По времени
Для того чтобы найти ответ, нам достаточно один раз пройтись по исходному массиву со сложностью O(n).
Однако предварительно мы еще делаем сортировку массива, сложность которой скорее всего равна O(n⋅logn).
Поэтому итоговая сложность по времени равна O(n⋅logn).
По памяти
Мы не создаем переменных, зависящих от длины входных данных, однако для сортировки также требуется выделение памяти.
В разных языках используются разные алгоритмы, поэтому сложность по памяти будет варьироваться от O(logn) до O(n).
