← Назад к вопросам
Объедините пересекающиеся интервалы
2.0 Middle🔥 61 комментариев
#Алгоритмы и структуры данных
Условие
Дан массив интервалов, где каждый интервал представлен парой [start, end]. Напишите функцию, которая объединяет все пересекающиеся интервалы:
function mergeIntervals(intervals) {
// Ваш код
}
// Примеры:
console.log(mergeIntervals([[1,3], [2,6], [8,10], [15,18]]));
// [[1,6], [8,10], [15,18]]
console.log(mergeIntervals([[1,4], [4,5]]));
// [[1,5]]
console.log(mergeIntervals([[1,4], [0,4]]));
// [[0,4]]
Что проверяется
- Алгоритмическое мышление
- Работа с массивами
- Сортировка и итерация
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
Базовая реализация
function mergeIntervals(intervals: number[][]): number[][] {
// Если интервалов меньше 2, возвращаем как есть
if (intervals.length <= 1) {
return intervals;
}
// Сортируем интервалы по началу
intervals.sort((a, b) => a[0] - b[0]);
const result: number[][] = [intervals[0]];
for (let i = 1; i < intervals.length; i++) {
const current = intervals[i];
const last = result[result.length - 1];
// Если текущий интервал пересекается с последним, объединяем
if (current[0] <= last[1]) {
last[1] = Math.max(last[1], current[1]);
} else {
// Иначе добавляем как новый интервал
result.push(current);
}
}
return result;
}
Как это работает
Принцип работы:
- Сортируем по началу — интервалы сортируются по первому элементу (start)
- Инициализируем результат — первый интервал добавляем в результат
- Итерируем оставшиеся — для каждого следующего интервала:
- Если он пересекается с последним (current[0] <= last[1]), объединяем
- Иначе добавляем как новый интервал
- Обновляем конец — берём максимум из двух концов
Пошаговый процесс:
Вход: [[1,3], [2,6], [8,10], [15,18]]
Шаг 1: Сортируем
[[1,3], [2,6], [8,10], [15,18]] → уже отсортирован
Шаг 2: Инициализируем результат
result = [[1,3]]
Шаг 3: Обрабатываем [2,6]
current[0]=2, last[1]=3
2 <= 3 → пересекаются
last[1] = max(3, 6) = 6
result = [[1,6]]
Шаг 4: Обрабатываем [8,10]
current[0]=8, last[1]=6
8 <= 6 → false, не пересекаются
result.push([8,10])
result = [[1,6], [8,10]]
Шаг 5: Обрабатываем [15,18]
current[0]=15, last[1]=10
15 <= 10 → false, не пересекаются
result.push([15,18])
result = [[1,6], [8,10], [15,18]]
Результат: [[1,6], [8,10], [15,18]]
Примеры использования
Пример 1: Пересекающиеся интервалы
mergeIntervals([[1,3], [2,6], [8,10], [15,18]]);
// [[1,6], [8,10], [15,18]]
Пример 2: Соприкасающиеся интервалы
mergeIntervals([[1,4], [4,5]]);
// [[1,5]]
Пример 3: Полностью перекрывающиеся
mergeIntervals([[1,4], [0,4]]);
// [[0,4]]
Пример 4: Вложенные интервалы
mergeIntervals([[1,5], [2,3], [4,4]]);
// [[1,5]]
Версия с проверкой пересечения функцией
function intervalsOverlap(a: number[], b: number[]): boolean {
return a[0] <= b[1] && b[0] <= a[1];
}
function mergeIntervals(intervals: number[][]): number[][] {
if (intervals.length <= 1) return intervals;
intervals.sort((a, b) => a[0] - b[0]);
const result: number[][] = [intervals[0]];
for (let i = 1; i < intervals.length; i++) {
const current = intervals[i];
const last = result[result.length - 1];
if (intervalsOverlap(last, current)) {
last[1] = Math.max(last[1], current[1]);
} else {
result.push(current);
}
}
return result;
}
Версия с reduce
function mergeIntervals(intervals: number[][]): number[][] {
if (intervals.length <= 1) return intervals;
return intervals
.sort((a, b) => a[0] - b[0])
.reduce((result, current) => {
const last = result[result.length - 1];
if (current[0] <= last[1]) {
last[1] = Math.max(last[1], current[1]);
} else {
result.push(current);
}
return result;
});
}
Более сложный пример с деталями
interface Interval {
start: number;
end: number;
id?: string;
}
function mergeIntervalsWithMetadata(
intervals: Interval[]
): Interval[] {
if (intervals.length <= 1) return intervals;
intervals.sort((a, b) => a.start - b.start);
const result: Interval[] = [{ ...intervals[0] }];
for (let i = 1; i < intervals.length; i++) {
const current = intervals[i];
const last = result[result.length - 1];
if (current.start <= last.end) {
// Объединяем интервалы
last.end = Math.max(last.end, current.end);
if (current.id) {
last.id = `${last.id || 'merged'},${current.id}`;
}
} else {
result.push({ ...current });
}
}
return result;
}
// Пример:
const intervals = [
{ start: 1, end: 3, id: 'A' },
{ start: 2, end: 6, id: 'B' },
{ start: 8, end: 10, id: 'C' }
];
mergeIntervalsWithMetadata(intervals);
// [
// { start: 1, end: 6, id: 'A,B' },
// { start: 8, end: 10, id: 'C' }
// ]
Тестирование
import { describe, it, expect } from 'vitest';
describe('mergeIntervals', () => {
it('должен объединить пересекающиеся интервалы', () => {
const result = mergeIntervals([[1,3], [2,6], [8,10], [15,18]]);
expect(result).toEqual([[1,6], [8,10], [15,18]]);
});
it('должен объединить соприкасающиеся интервалы', () => {
const result = mergeIntervals([[1,4], [4,5]]);
expect(result).toEqual([[1,5]]);
});
it('должен объединить вложенные интервалы', () => {
const result = mergeIntervals([[1,4], [0,4]]);
expect(result).toEqual([[0,4]]);
});
it('должен обработать пустой массив', () => {
const result = mergeIntervals([]);
expect(result).toEqual([]);
});
it('должен обработать один интервал', () => {
const result = mergeIntervals([[1,5]]);
expect(result).toEqual([[1,5]]);
});
it('должен обработать не пересекающиеся интервалы', () => {
const result = mergeIntervals([[1,2], [3,4], [5,6]]);
expect(result).toEqual([[1,2], [3,4], [5,6]]);
});
it('должен обработать вложенные интервалы', () => {
const result = mergeIntervals([[1,5], [2,3], [4,4]]);
expect(result).toEqual([[1,5]]);
});
it('должен обработать неотсортированные интервалы', () => {
const result = mergeIntervals([[8,10], [2,6], [1,3], [15,18]]);
expect(result).toEqual([[1,6], [8,10], [15,18]]);
});
});
Анализ сложности
Временная сложность:
- Сортировка: O(n log n)
- Итерация: O(n)
- Итого: O(n log n)
Пространственная сложность:
- Результат: O(n) в худшем случае (нет пересечений)
- Итого: O(n)
Практические применения
1. Расписания и занятость:
const busyHours = [[9,11], [13,15], [14,16], [17,19]];
const merged = mergeIntervals(busyHours);
// [[9,11], [13,16], [17,19]]
// Найти свободное время
2. Диапазоны IP адресов:
const ipRanges = [
[192168000001, 192168000255],
[192168001000, 192168001255],
[192168002000, 192168002255]
];
const merged = mergeIntervals(ipRanges);
3. Сегменты видео:
const subtitles = [
[0, 5000], // 0-5 сек
[4000, 8000], // 4-8 сек
[10000, 15000] // 10-15 сек
];
const merged = mergeIntervals(subtitles);
// [[0, 8000], [10000, 15000]]
4. Резервирование номеров:
const reservations = [
[2024010101, 2024010105],
[2024010104, 2024010108],
[2024010110, 2024010115]
];
const merged = mergeIntervals(reservations);
// [[2024010101, 2024010108], [2024010110, 2024010115]]
Альтернативные подходы
Подход с событиями (событие start/end):
function mergeIntervalsWithEvents(intervals: number[][]): number[][] {
const events: [number, 'start' | 'end'][] = [];
intervals.forEach(([start, end]) => {
events.push([start, 'start']);
events.push([end, 'end']);
});
events.sort((a, b) => a[0] !== b[0] ? a[0] - b[0] : a[1] === 'start' ? -1 : 1);
const result: number[][] = [];
let count = 0;
let start = 0;
for (const [time, type] of events) {
if (type === 'start') {
if (count === 0) start = time;
count++;
} else {
count--;
if (count === 0) result.push([start, time]);
}
}
return result;
}
Этот подход работает за O(n log n) и может быть полезен, если нужны дополнительные метаданные.