Merge Intervals
Условие
Дан массив интервалов intervals, где intervals[i] = [start_i, end_i]. Объедините все пересекающиеся интервалы и верните массив непересекающихся интервалов, которые покрывают все интервалы входных данных.
Примеры:
Пример 1:
- Вход: intervals = [[1,3], [2,6], [8,10], [15,18]]
- Выход: [[1,6], [8,10], [15,18]]
- Объяснение: Интервалы [1,3] и [2,6] пересекаются, объединяем в [1,6]
Пример 2:
- Вход: intervals = [[1,4], [4,5]]
- Выход: [[1,5]]
- Объяснение: Интервалы [1,4] и [4,5] считаются пересекающимися
Ограничения:
- 1 <= intervals.length <= 10^4
- intervals[i].length == 2
- 0 <= start_i <= end_i <= 10^4
Подсказка: Сначала отсортируйте интервалы по начальной точке, затем итерируйте и объединяйте.
Задача часто встречается на алгоритмических секциях в крупных IT-компаниях.
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
Оптимальное решение O(n log n) время
func mergeIntervals(_ intervals: [[Int]]) -> [[Int]] {
guard !intervals.isEmpty else { return [] }
// Сортируем по начальной точке
let sorted = intervals.sorted { $0[0] < $1[0] }
var merged = [sorted[0]]
for i in 1..<sorted.count {
let current = sorted[i]
let last = merged[merged.count - 1]
// Если текущий интервал пересекается с последним объединённым
if current[0] <= last[1] {
// Объединяем: расширяем конец последнего интервала
merged[merged.count - 1] = [last[0], max(last[1], current[1])]
} else {
// Нет пересечения, добавляем новый интервал
merged.append(current)
}
}
return merged
}
Пошаговое выполнение для примера 1:
Входные данные: [[1,3], [2,6], [8,10], [15,18]]
Шаг 1: Сортировка
Отсортировано: [[1,3], [2,6], [8,10], [15,18]]
Шаг 2: Инициализация
merged = [[1,3]]
Шаг 3: Итерация
i=1: current=[2,6], last=[1,3]
2 <= 3? ДА → Объединяем
merged = [[1, max(3,6)]] = [[1,6]]
i=2: current=[8,10], last=[1,6]
8 <= 6? НЕТ → Добавляем
merged = [[1,6], [8,10]]
i=3: current=[15,18], last=[8,10]
15 <= 10? НЕТ → Добавляем
merged = [[1,6], [8,10], [15,18]]
Результат: [[1,6], [8,10], [15,18]] ✓
Альтернативное решение со структурой Interval
struct Interval {
let start: Int
let end: Int
}
func mergeIntervals(_ intervals: [Interval]) -> [Interval] {
guard !intervals.isEmpty else { return [] }
let sorted = intervals.sorted { $0.start < $1.start }
var merged = [sorted[0]]
for interval in sorted.dropFirst() {
let last = merged.removeLast()
if interval.start <= last.end {
merged.append(Interval(
start: last.start,
end: max(last.end, interval.end)
))
} else {
merged.append(last)
merged.append(interval)
}
}
return merged
}
Тестирование
let test1 = [[1,3], [2,6], [8,10], [15,18]]
print(mergeIntervals(test1)) // [[1,6], [8,10], [15,18]]
let test2 = [[1,4], [4,5]]
print(mergeIntervals(test2)) // [[1,5]]
// Дополнительные тесты
let test3: [[Int]] = [] // Пустой массив
print(mergeIntervals(test3)) // []
let test4 = [[1,5]] // Один интервал
print(mergeIntervals(test4)) // [[1,5]]
let test5 = [[0,0]] // Нулевая длина
print(mergeIntervals(test5)) // [[0,0]]
let test6 = [[1,2], [3,4], [5,6]] // Без пересечений
print(mergeIntervals(test6)) // [[1,2], [3,4], [5,6]]
let test7 = [[1,5], [1,6], [1,7]] // Все пересекаются в одной точке
print(mergeIntervals(test7)) // [[1,7]]
let test8 = [[2,3], [4,5], [6,7], [8,9], [1,10]] // Один большой интервал
print(mergeIntervals(test8)) // [[1,10]]
Анализ сложности
Временная сложность:
- Сортировка: O(n log n)
- Итерация: O(n)
- Общее: O(n log n)
Пространственная сложность: O(n)
- Массив
mergedможет содержать до n интервалов
Граничные случаи
- Пустой массив - вернуть пустой массив
- Один интервал - вернуть его как есть
- Все пересекаются - вернуть один большой интервал
- Никто не пересекается - вернуть отсортированный входной массив
- Одинаковые интервалы - вернуть один интервал
Почему именно эта логика?
Ключевое наблюдение: если интервалы отсортированы по началу, то для каждого интервала нам нужно проверить только с последним объединённым интервалом:
- Если
current[0] <= last[1]→ пересекаются - Используем
max(last[1], current[1])чтобы найти конец объединённого интервала
Вариация с использованием свопа
func mergeIntervalsInPlace(_ intervals: inout [[Int]]) -> [[Int]] {
guard !intervals.isEmpty else { return [] }
intervals.sort { $0[0] < $1[0] }
var writeIndex = 0
for readIndex in 1..<intervals.count {
if intervals[readIndex][0] <= intervals[writeIndex][1] {
intervals[writeIndex][1] = max(
intervals[writeIndex][1],
intervals[readIndex][1]
)
} else {
writeIndex += 1
intervals[writeIndex] = intervals[readIndex]
}
}
return Array(intervals[0...writeIndex])
}
Этот подход более оптимален по памяти для больших данных.
Рекомендация для интервью
Используйте первое решение:
- Оно ясно показывает логику
- Легко объяснить интервьюеру
- Правильно обрабатывает все граничные случаи
- O(n log n) достаточно оптимально