0Pricing
Coding Interview Prep · Aula

Escalonamento e fusão de intervalos

Resolva os problemas de salas de reunião e intervalos não sobrepostos ordenando pelo horário de término, e funda intervalos ordenando pelo horário de início.

Escalonamento e fusão de intervalos é uma aula grátis de Coding Interview Prep no CoddyKit. Esta é a aula 2 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de Coding Interview Prep, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de Coding Interview Prep inclui 4 aulas no total.

Visão geral dos problemas de intervalos

Problemas de intervalos aparecem constantemente em entrevistas sobre escalonamento, gerenciamento de calendários e alocação de recursos. Os padrões principais são: mesclar intervalos sobrepostos, contar o número mínimo de salas de reunião, encontrar o maior conjunto de intervalos não sobrepostos e inserir um novo intervalo. A maioria dos problemas de intervalos começa pela mesma etapa: sort os intervalos pelo tempo de início (ou pelo tempo de término, dependendo do problema). Escolher a chave de ordenação correta costuma ser a parte mais difícil.

# Intervals: each = [start, end] (inclusive or exclusive by problem)
# Example:
intervals = [[1,3],[2,6],[8,10],[15,18]]
# Sorted by start (already sorted here)
# Visually:
# [1,3]    |-|
# [2,6]      |---|
# [8,10]             |--|
# [15,18]                    |---|
print('Intervals ready for analysis')

Mesclar intervalos sobrepostos

Mesclar intervalos (LeetCode 56): dada uma lista de intervalos, mescle todos os que se sobrepõem. Algoritmo: sort pelo tempo de início. Percorra a lista ordenada; se o intervalo atual se sobrepuser ao último intervalo mesclado (seu início ≤ end do último intervalo mesclado), estenda o end do último intervalo mesclado até o máximo dos dois extremos. Caso contrário, use append para adicionar o intervalo atual como um novo intervalo mesclado. Tempo: O(n log n) para a ordenação e O(n) para a mesclagem.

def merge_intervals(intervals):
    intervals.sort(key=lambda x: x[0])  # sort by start
    merged = [intervals[0]]
    for start, end in intervals[1:]:
        last_end = merged[-1][1]
        if start <= last_end:
            # Overlapping: extend the last interval
            merged[-1][1] = max(last_end, end)
        else:
            # Non-overlapping: add as new interval
            merged.append([start, end])
    return merged

print(merge_intervals([[1,3],[2,6],[8,10],[15,18]]))
# [[1,6],[8,10],[15,18]]
print(merge_intervals([[1,4],[4,5]]))
# [[1,5]] (touching intervals merge)

Inserir intervalo

Inserir intervalo (LeetCode 57): dada uma lista ordenada e não sobreposta, insira um novo intervalo e faça a mesclagem novamente. Percorra a lista em três fases: (1) adicione todos os intervalos que terminam antes do início do novo intervalo; (2) mescle todos os intervalos que se sobrepõem ao novo intervalo (expandindo seus limites); (3) adicione todos os intervalos restantes. Essa é uma única passagem O(n) depois da operação de sort O(n log n), que já foi realizada neste problema.

def insert_interval(intervals, new_interval):
    result = []
    i = 0
    n = len(intervals)
    # Phase 1: intervals before new_interval
    while i < n and intervals[i][1] < new_interval[0]:
        result.append(intervals[i])
        i += 1
    # Phase 2: merge overlapping intervals
    while i < n and intervals[i][0] <= new_interval[1]:
        new_interval[0] = min(new_interval[0], intervals[i][0])
        new_interval[1] = max(new_interval[1], intervals[i][1])
        i += 1
    result.append(new_interval)
    # Phase 3: remaining intervals
    while i < n:
        result.append(intervals[i])
        i += 1
    return result

print(insert_interval([[1,3],[6,9]], [2,5]))  # [[1,5],[6,9]]
print(insert_interval([[1,2],[3,5],[6,7],[8,10],[12,16]], [4,8]))
# [[1,2],[3,10],[12,16]]

Salas de reunião I: é possível participar de todas?

Salas de reunião I (LeetCode 252): dados os intervalos de tempo das reuniões, determine se uma pessoa pode participar de todas elas. Ordene pelo tempo de início; se alguma reunião começar antes de a anterior terminar, haverá sobreposição. Esta é a verificação de intervalos mais simples — O(n log n) no total. A ideia principal é que, depois da ordenação, basta comparar pares consecutivos.

def can_attend_meetings(intervals):
    intervals.sort(key=lambda x: x[0])
    for i in range(1, len(intervals)):
        # Current meeting starts before previous ends?
        if intervals[i][0] < intervals[i-1][1]:
            return False
    return True

print(can_attend_meetings([[0,30],[5,10],[15,20]]))  # False (0,30 overlaps 5,10)
print(can_attend_meetings([[7,10],[2,4]]))           # True (4 < 7, no overlap)

Salas de reunião II: número mínimo de salas

Salas de reunião II (LeetCode 253): encontre o número mínimo de salas de conferência necessárias para realizar todas as reuniões simultaneamente. Use um montículo mínimo para acompanhar a sala cujo término ocorre primeiro. Ordene as reuniões pelo tempo de início. Para cada nova reunião: se ela começar depois do horário de término da sala que termina primeiro, reutilize essa sala (faça pop e depois insira o novo término). Caso contrário, abra uma nova sala. O tamanho do montículo ao final será igual ao número de salas necessárias.

import heapq

def min_meeting_rooms(intervals):
    if not intervals: return 0
    intervals.sort(key=lambda x: x[0])  # sort by start
    heap = []  # min-heap of end times
    for start, end in intervals:
        if heap and heap[0] <= start:
            heapq.heapreplace(heap, end)  # reuse earliest-ending room
        else:
            heapq.heappush(heap, end)     # open a new room
    return len(heap)

print(min_meeting_rooms([[0,30],[5,10],[15,20]]))  # 2
print(min_meeting_rooms([[7,10],[2,4]]))           # 1
print(min_meeting_rooms([[9,10],[4,9],[4,17]]))    # 2

Alternativa da linha de varredura para contar salas

Uma abordagem alternativa O(n log n): a linha de varredura. Crie eventos para o início (+1) e o fim (-1) de cada intervalo. Ordene todos os eventos por time (em caso de empate: end antes de início se quiser um comportamento não inclusivo). Varra da esquerda para a direita, mantendo uma contagem acumulada das reuniões ativas. A contagem máxima é o número mínimo de salas necessárias. Essa abordagem é mais intuitiva para algumas pessoas e generaliza-se para outros problemas de contagem em intervalos.

def min_rooms_sweep(intervals):
    events = []
    for start, end in intervals:
        events.append((start, 1))   # meeting starts
        events.append((end, -1))    # meeting ends
    # Sort: same time → end (-1) before start (1) if exclusive
    events.sort(key=lambda x: (x[0], x[1]))
    max_rooms = current = 0
    for _, delta in events:
        current += delta
        max_rooms = max(max_rooms, current)
    return max_rooms

print(min_rooms_sweep([[0,30],[5,10],[15,20]]))  # 2
print(min_rooms_sweep([[1,5],[2,6],[3,7]]))       # 3 (all overlap at t=3)

Intervalos não sobrepostos: seleção máxima

Intervalos não sobrepostos (LeetCode 435): encontre o número mínimo de intervalos aos quais aplicar remove para que os restantes não se sobreponham. Isso equivale a encontrar o número máximo de intervalos não sobrepostos (seleção de atividades) e retornar o restante como remoções. Ordene pelo tempo de término: mantenha de forma gulosa o intervalo que termina mais cedo (maximizando o espaço para intervalos futuros). Quando o próximo intervalo se sobrepuser, use discard nele (contabilize uma remoção).

def erase_overlap_intervals(intervals):
    if not intervals: return 0
    intervals.sort(key=lambda x: x[1])  # sort by END time
    removals = 0
    last_end = float('-inf')
    for start, end in intervals:
        if start >= last_end:
            last_end = end  # keep this interval
        else:
            removals += 1   # remove this interval (it overlaps)
    return removals

print(erase_overlap_intervals([[1,2],[2,3],[3,4],[1,3]]))  # 1 (remove [1,3])
print(erase_overlap_intervals([[1,2],[1,2],[1,2]]))        # 2
print(erase_overlap_intervals([[1,2],[2,3]]))              # 0 (no overlap)

Por que ordenar pelo horário de término, e não pelo de início?

Na seleção de atividades (conjunto máximo de atividades sem sobreposição), ordenar pelo horário de término é comprovadamente ótimo. Intuição: uma atividade que termina cedo deixa mais espaço para atividades futuras. Se ordenarmos pelo horário de início, poderemos escolher uma atividade muito longa que começa cedo e bloqueia muitas atividades posteriores mais curtas. Argumento de troca: se a solução ótima escolher a atividade A em vez de G, que termina primeiro, troque A por G — G não termina mais tarde, portanto não entra em conflito com nada com que A não entrasse em conflito.

# Counterexample for sorting by START time:
# [[1,10],[2,3],[4,5]] — sorted by start: [1,10],[2,3],[4,5]
# Sort-by-start greedy keeps [1,10], can't add [2,3] or [4,5] (all overlap [1,10])
# Selects: 1 interval

# Sort-by-end greedy:
# [[2,3],[4,5],[1,10]] — sorted by end
# Keep [2,3] (end=3), then [4,5] (start=4 >= 3, keep), then [1,10] (start=1 < 5, skip)
# Selects: 2 intervals — OPTIMAL

intervals = [[1,10],[2,3],[4,5]]
intervals.sort(key=lambda x: x[1])
last_end = float('-inf')
count = 0
for s, e in intervals:
    if s >= last_end:
        count += 1; last_end = e
print('Max non-overlapping:', count)  # 2

Interseções de Listas de Intervalos

Interseções de Listas de Intervalos (LeetCode 986): encontre todos os pares que se intersectam em duas listas de intervalos ordenadas. Use uma abordagem de dois ponteiros. A cada etapa, calcule a interseção do par atual (máximo dos inícios, mínimo dos términos). Se início ≤ término, a interseção é válida. Em seguida, avance o ponteiro do intervalo que termina primeiro. Tempo O(m+n).

def interval_intersection(A, B):
    result = []
    i = j = 0
    while i < len(A) and j < len(B):
        # Intersection boundaries
        lo = max(A[i][0], B[j][0])
        hi = min(A[i][1], B[j][1])
        if lo <= hi:
            result.append([lo, hi])  # valid intersection
        # Advance pointer of interval that ends first
        if A[i][1] < B[j][1]:
            i += 1
        else:
            j += 1
    return result

A = [[0,2],[5,10],[13,23],[24,25]]
B = [[1,5],[8,12],[15,24],[25,26]]
print(interval_intersection(A, B))
# [[1,2],[5,5],[8,10],[15,23],[24,24],[25,25]]

Particionamento de Rótulos

Particionamento de Rótulos (LeetCode 763): divida uma cadeia de caracteres no maior número possível de partes, de modo que cada caractere apareça em no máximo uma parte. Abordagem gulosa: para cada caractere, encontre sua última ocorrência. Percorra a cadeia mantendo um max_end. Quando i == max_end, a partição atual está completa — registre seu comprimento e comece uma nova partição. Este é, disfarçadamente, um problema de fusão de intervalos.

def partition_labels(s):
    last = {c: i for i, c in enumerate(s)}  # last occurrence of each char
    partitions = []
    start = max_end = 0
    for i, c in enumerate(s):
        max_end = max(max_end, last[c])
        if i == max_end:  # partition complete
            partitions.append(max_end - start + 1)
            start = i + 1
    return partitions

print(partition_labels('ababcbacadefegdehijhklij'))
# [9, 7, 8] — parts 'ababcbaca', 'defegde', 'hijhklij'

Resumo dos Problemas de Intervalos

Domine estes quatro padrões para intervalos: (1) Fundir: ordene pelo início e estenda o último se houver sobreposição. (2) Contar salas: ordene pelo início e use um monte mínimo dos horários de término. (3) Máximo sem sobreposição: ordene pelo término e selecione de forma gulosa. (4) Inserir: faça uma varredura linear em três fases. A chave de ordenação é importante: a fusão usa o início; a seleção máxima usa o término. A complexidade temporal é sempre O(n log n), dominada pela ordenação; a fusão e a varredura são O(n).

# Quick reference:
# Merge intervals:         sort by start, extend if overlap
# Insert interval:         three-phase linear scan
# Meeting rooms (can?):   sort by start, check consecutive overlap
# Meeting rooms (min?):   sort by start, min-heap of end times / sweep
# Max non-overlapping:    sort by END, greedy keep
# Min removals:           n - max_non_overlapping
# Interval intersection:  two pointers on sorted lists

print('Pattern: sort key is the decisive choice')
print('Merge → sort by start')
print('Activity selection → sort by end')
print('Room count → sort by start + heap of ends')

Verificação Rápida

Teste sua compreensão dos conceitos de Estruturas de Dados e Algoritmos — Preparação para Entrevistas de Programação desta lição.

Recapitulação da Lição

Nesta lição, você aprendeu: fundir intervalos ordenando-os pelo início e estendendo o último intervalo quando houver sobreposição, encontrar o número mínimo de salas para reuniões usando ordenação pelo início e um monte mínimo dos horários de término, reutilizando salas quando a sala que termina primeiro fica livre e encontrar o número máximo de intervalos sem sobreposição usando seleção gulosa ordenada pelo horário de término. A seguir, abordaremos o Jogo de Saltos I e II — problemas de alcançabilidade e de número mínimo de saltos resolvidos com expansão gulosa do alcance.

Perguntas Frequentes

A aula “Escalonamento e fusão de intervalos” é grátis?

Sim — o texto completo de “Escalonamento e fusão de intervalos” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de Coding Interview Prep, atualize para CoddyKit PRO. O curso de Coding Interview Prep inclui 4 aulas no total.

O que vou aprender em “Escalonamento e fusão de intervalos”?

Resolva os problemas de salas de reunião e intervalos não sobrepostos ordenando pelo horário de término, e funda intervalos ordenando pelo horário de início. Você pratica Coding Interview Prep com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar Coding Interview Prep?

Nenhuma experiência prévia é necessária. Coding Interview Prep no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 2 de 4.

Quanto tempo leva a aula “Escalonamento e fusão de intervalos”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de Coding Interview Prep?

Sim. Cada aula de Coding Interview Prep inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. Algoritmos gulosos vs DP: quando usar cada um
  2. Escalonamento e fusão de intervalos
  3. Jogo dos saltos I e II
  4. Escalonador de tarefas e posto de combustível
← Voltar para Coding Interview Prep