← Назад к вопросам

Объединение интервалов

2.0 Middle🔥 131 комментариев
#Теория тестирования

Условие

Дан массив интервалов, где каждый интервал представлен парой [start, end]. Объедините все пересекающиеся интервалы.

Пример

Вход: [[1,3], [2,6], [8,10], [15,18]] Выход: [[1,6], [8,10], [15,18]]

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение

Понимание задачи

Объединить интервалы, которые пересекаются или соприкасаются. Например:

  • [1,3] и [2,6] пересекаются -> [1,6]
  • [1,5] и [5,10] соприкасаются -> [1,10]
  • [1,5] и [7,10] не пересекаются -> оставить как есть

Оптимальный алгоритм: Sort + Merge

def merge_intervals(intervals):
    """
    Объединяет пересекающиеся интервалы.
    """
    if not intervals:
        return []
    
    # Сортируем по началу интервала
    intervals.sort(key=lambda x: x[0])
    
    merged = [intervals[0]]
    
    for current in intervals[1:]:
        last = merged[-1]
        
        # Если текущий пересекается с последним
        if current[0] <= last[1]:
            # Объединяем: расширяем конец
            merged[-1] = [last[0], max(last[1], current[1])]
        else:
            # Не пересекается: добавляем как новый
            merged.append(current)
    
    return merged

Как это работает

Пример: [[1,3], [2,6], [8,10], [15,18]]

Шаг 1: Сортировка по началу (уже отсортировано) Шаг 2: merged = [[1, 3]] Шаг 3: Обработка [2, 6]

  • 2 <= 3 (пересекается)
  • Объединяем: [1, max(3, 6)] = [1, 6]
  • merged = [[1, 6]] Шаг 4: Обработка [8, 10]
  • 8 > 6 (не пересекается)
  • Добавляем: merged = [[1, 6], [8, 10]] Шаг 5: Обработка [15, 18]
  • 15 > 10 (не пересекается)
  • Добавляем: merged = [[1, 6], [8, 10], [15, 18]]

Результат: [[1, 6], [8, 10], [15, 18]]

Сложность

  • Временная: O(n log n) — сортировка O(n log n) + проход O(n)
  • Пространственная: O(1) или O(n) — зависит от требования (место для результата)

Расширенная версия с обработкой ошибок

def merge_intervals_advanced(intervals):
    """
    Объединяет интервалы с обработкой граничных случаев.
    """
    # Пустой список
    if not intervals:
        return []
    
    # Валидация входных данных
    for interval in intervals:
        if not isinstance(interval, (list, tuple)) or len(interval) != 2:
            raise ValueError("Каждый интервал должен быть парой [start, end]")
        if interval[0] > interval[1]:
            raise ValueError("Start не может быть больше end")
    
    # Сортируем по началу, затем по концу
    intervals.sort(key=lambda x: (x[0], x[1]))
    
    merged = [list(intervals[0])]
    
    for current in intervals[1:]:
        last = merged[-1]
        
        if current[0] <= last[1]:
            # Объединяем интервалы
            merged[-1][1] = max(last[1], current[1])
        else:
            # Новый интервал
            merged.append(list(current))
    
    return merged

Тестирование

# Базовые случаи
assert merge_intervals([[1,3],[2,6],[8,10],[15,18]]) == [[1,6],[8,10],[15,18]]
assert merge_intervals([[1,4],[4,5]]) == [[1,5]]
assert merge_intervals([[1,2],[3,4]]) == [[1,2],[3,4]]

# Пересекающиеся интервалы
assert merge_intervals([[1,5],[2,3]]) == [[1,5]]
assert merge_intervals([[1,4],[2,3]]) == [[1,4]]

# Вложенные интервалы
assert merge_intervals([[1,10],[2,3],[4,5],[6,7]]) == [[1,10]]

# Один интервал
assert merge_intervals([[1,5]]) == [[1,5]]

# Пустой список
assert merge_intervals([]) == []

# Несортированные
assert merge_intervals([[3,4],[1,2],[5,6]]) == [[1,2],[3,4],[5,6]]

# Соприкасающиеся
assert merge_intervals([[1,4],[4,5]]) == [[1,5]]

Альтернативные подходы

Способ 1: С кучей (Priority Queue)

import heapq

def merge_intervals_heap(intervals):
    if not intervals:
        return []
    
    # Используем минимальную кучу
    heap = [[end, start] for start, end in intervals]
    heapq.heapify(heap)
    
    merged = [[heapq.heappop(heap)[1], heapq.heappop(heap)[0]]]
    
    while heap:
        current_end, current_start = heapq.heappop(heap)
        last_start, last_end = merged[-1]
        
        if current_start <= last_end:
            merged[-1][1] = max(last_end, current_end)
        else:
            merged.append([current_start, current_end])
    
    return merged
  • O(n log n) время
  • O(n) память
  • Более сложно

Способ 2: С использованием координат (Sweep Line)

def merge_intervals_sweep(intervals):
    if not intervals:
        return []
    
    events = []
    for start, end in intervals:
        events.append((start, 0))  # 0 для начала
        events.append((end, 1))    # 1 для конца
    
    events.sort()
    merged = []
    count = 0
    start = 0
    
    for pos, event_type in events:
        if event_type == 0:
            if count == 0:
                start = pos
            count += 1
        else:
            count -= 1
            if count == 0:
                merged.append([start, pos])
    
    return merged
  • O(n log n) время
  • Более сложный алгоритм

Граничные случаи

# Пустой список
assert merge_intervals([]) == []

# Один интервал
assert merge_intervals([[1,5]]) == [[1,5]]

# Все вложены
assert merge_intervals([[1,100],[2,3],[4,5]]) == [[1,100]]

# Не пересекаются
assert merge_intervals([[1,2],[3,4],[5,6]]) == [[1,2],[3,4],[5,6]]

# Идентичные интервалы
assert merge_intervals([[1,5],[1,5],[1,5]]) == [[1,5]]

# Соприкасаются по границам
assert merge_intervals([[1,3],[3,5]]) == [[1,5]]
assert merge_intervals([[1,3],[4,5]]) == [[1,3],[4,5]]

# Большой диапазон
assert merge_intervals([[0,1000000],[1,5]]) == [[0,1000000]]

Выводы

  • Sort + Merge — оптимальное решение
  • O(n log n) время — доминирует сортировка
  • O(1) доп. память — если не считать результата
  • Ключевой момент: после сортировки можно эффективно объединять
  • Для QA: проверить все типы пересечений, граничные случаи, несортированные входы