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]])) # 2Alternativa 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) # 2Interseçõ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
- Algoritmos gulosos vs DP: quando usar cada um
- Escalonamento e fusão de intervalos
- Jogo dos saltos I e II
- Escalonador de tarefas e posto de combustível