Escalonador de tarefas e posto de combustível
Aplique o raciocínio guloso ao problema do período de resfriamento do escalonador de tarefas da CPU e ao problema de viabilidade de postos de combustível em um circuito.
Escalonador de tarefas e posto de combustível é uma aula grátis de DSA Interview Prep no CoddyKit. Esta é a aula 4 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 DSA Interview Prep, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de DSA Interview Prep inclui 4 aulas no total.
Problema do Agendador de Tarefas
Agendador de Tarefas (LeetCode 621): dada uma lista de tarefas da CPU (cada uma identificada por A-Z) e um período de espera n, encontre o número mínimo de intervalos da CPU necessários para concluir todas as tarefas. A mesma tarefa deve esperar pelo menos n intervalos antes de ser executada novamente. Intervalos ociosos são permitidos. Para as tarefas ['A','A','A','B','B','B'] com período de espera 2, a resposta é 8: A→B→idle→A→B→idle→A→B.
# Task Scheduler example
tasks = ['A','A','A','B','B','B']
n = 2 # cooldown
# One optimal schedule: A B _ A B _ A B
# Intervals: 1 2 3 4 5 6 7 8 → answer = 8
# Another example: tasks=['A','A','A','B','B','C'] n=2
# A B C A B _ A → 7 intervals
print('Understanding the cooldown constraint')
print('Same task needs n intervals gap between runs')Fórmula Gulosa para o Agendador de Tarefas
A ideia principal é que o tempo total é determinado pela tarefa mais frequente. Se a tarefa mais frequente aparecer f vezes, com contagem max_count (número de tarefas com frequência f), o tempo será max(len(tasks), (f-1) * (n+1) + max_count). A fórmula consiste em criar f-1 blocos de tamanho n+1, preenchê-los com outras tarefas e adicionar o último ciclo. Se as outras tarefas preencherem todos os intervalos ociosos (muitas tarefas diferentes), basta executar todas as tarefas sem tempo ocioso.
from collections import Counter
def least_interval(tasks, n):
count = Counter(tasks)
max_freq = max(count.values())
# How many tasks have the maximum frequency?
max_count = sum(1 for c in count.values() if c == max_freq)
# Formula: max of total tasks (no idle) or frame-based calculation
frame_time = (max_freq - 1) * (n + 1) + max_count
return max(len(tasks), frame_time)
print(least_interval(['A','A','A','B','B','B'], 2)) # 8
print(least_interval(['A','A','A','B','B','B'], 0)) # 6 (no cooldown)
print(least_interval(['A','A','A','A','B','C'], 3)) # 10Por que a Fórmula Funciona
Visualize o cronograma como uma grade com n+1 colunas (um espaço para tarefa + n espaços de espera). A tarefa mais frequente A (frequência f) precisa de f linhas. Entre a primeira e a última ocorrência, há f-1 blocos completos de n+1 espaços. Além disso, há o último bloco parcial, que contém todas as tarefas com frequência máxima. Se houver tarefas diferentes suficientes, elas preencherão todos os espaços ociosos, e a quantidade real de tarefas excederá o tempo dos blocos — escolha o maior dos dois.
# Visualise frame structure for AAABBB, n=2
# Frame size = n+1 = 3
# f = 3 (A appears 3 times), max_count = 2 (A and B both appear 3 times)
# Grid:
# [A B _] ← frame 1
# [A B _] ← frame 2
# [A B ] ← last partial frame (max_count=2 cells)
# Total = (3-1)*3 + 2 = 6 + 2 = 8
# If tasks = AAAABBCC, n=2: max_freq=4 (A), max_count=1
# (4-1)*(2+1)+1 = 9+1 = 10
# But len(tasks)=8 < 10, so answer is 10
tasks2 = ['A','A','A','A','B','B','C','C']
from collections import Counter
count = Counter(tasks2)
mf = max(count.values())
mc = sum(1 for c in count.values() if c == mf)
print(f'Frame formula: ({mf}-1)*{2+1}+{mc} = {(mf-1)*(2+1)+mc}')
print(f'Max(len={len(tasks2)}, frame={max(len(tasks2),(mf-1)*3+mc)}) = {max(len(tasks2),(mf-1)*3+mc)}')Alternativa de Simulação com Monte
Uma simulação baseada em um monte fornece o cronograma real (não apenas a contagem). A cada etapa, escolha a tarefa disponível mais frequente (monte máximo). Após executá-la, aplique o período de espera: não a reinsira até n etapas depois. Use uma fila para acompanhar as tarefas em espera. Isso é executado em O(tempo total × log k), em que k é o número de tarefas distintas. Embora esteja correta, a fórmula é mais rápida. Conheça ambas — os entrevistadores podem pedir o próprio cronograma.
import heapq
from collections import deque, Counter
def task_scheduler_simulate(tasks, n):
count = Counter(tasks)
heap = [-c for c in count.values()] # max-heap using negation
heapq.heapify(heap)
time = 0
cooldown = deque() # (available_at, neg_count)
while heap or cooldown:
time += 1
if heap:
c = heapq.heappop(heap) + 1 # use one instance
if c < 0: # still has remaining tasks
cooldown.append((time + n, c))
if cooldown and cooldown[0][0] == time:
heapq.heappush(heap, cooldown.popleft()[1])
return time
print(task_scheduler_simulate(['A','A','A','B','B','B'], 2)) # 8Problema do Posto de Combustível
Posto de Combustível (LeetCode 134): há n postos de combustível dispostos em círculo. O posto i tem gas[i] de gasolina, e o custo para viajar até o posto seguinte é cost[i]. Começando com o tanque vazio, encontre o posto inicial a partir do qual é possível completar o circuito. Se não existir um posto assim, retorne -1. O problema garante que, se existir uma resposta válida, haverá no máximo uma.
# Example:
gas = [1, 2, 3, 4, 5]
cost = [3, 4, 5, 1, 2]
# net gain per station: gas[i] - cost[i]
net = [g - c for g, c in zip(gas, cost)]
print('Net gain per station:', net) # [-2, -2, -2, 3, 3]
# Only possible start: station 3 (index 3)
# Tank: 0 +3=3 → 3-1=2 → 2+1=3-2=... let's verify
print('Sum of net:', sum(net)) # 1 > 0 means solution existsSolução Gulosa para o Posto de Combustível
Algoritmo guloso: (1) Se a gasolina total < custo total, não existe solução (retorne -1). (2) Caso contrário, existe exatamente uma solução. Encontre-a com uma única passagem: acompanhe tank (combustível atual) e start (posto inicial candidato). Se tank < 0 depois de visitar um posto, o start atual não consegue chegar a esse posto — redefina tank = 0 e defina start = i + 1. O start final é a resposta.
def can_complete_circuit(gas, cost):
if sum(gas) < sum(cost):
return -1 # impossible
tank = 0
start = 0
for i in range(len(gas)):
tank += gas[i] - cost[i]
if tank < 0:
tank = 0
start = i + 1 # current start failed, try next
return start
gas = [1, 2, 3, 4, 5]
cost = [3, 4, 5, 1, 2]
print(can_complete_circuit(gas, cost)) # 3
gas2 = [2, 3, 4]
cost2 = [3, 4, 3]
print(can_complete_circuit(gas2, cost2)) # -1Por que o Início Guloso está Correto
Argumento de correção: se o tanque ficar negativo depois de chegar ao posto i partindo de start, nenhum posto entre start e i (inclusive) poderá ser um ponto de partida válido — todos terão menos combustível ao chegar ao posto i do que haveria partindo de start. Portanto, podemos ignorar todos esses postos com segurança e tentar i+1. Como existe uma solução (a gasolina total ≥ o custo total), o candidato final start deve funcionar.
# Proof sketch: why start=i+1 is correct after tank<0 at station i
# If we start at station j (start <= j <= i), tank at j is tank_from_start(j)
# After stations start..j: tank_from_j starts at 0, but we've already used gas[start..j-1]
# Starting at j means: tank_at_i = sum(net[j..i]) = sum(net[start..i]) - sum(net[start..j-1])
# Since sum(net[start..i]) < 0 AND sum(net[start..j-1]) >= 0 (no reset before i),
# tank_at_i when starting at j is even more negative → j cannot work either
def verify_gas_solution(gas, cost, start):
tank = 0
n = len(gas)
for i in range(n):
idx = (start + i) % n
tank += gas[idx] - cost[idx]
if tank < 0: return False
return True
print(verify_gas_solution([1,2,3,4,5],[3,4,5,1,2], 3)) # TrueForça Bruta versus Abordagem Gulosa para o Posto de Combustível
A força bruta testa cada posto inicial e simula o circuito completo — tempo O(n²). A solução gulosa de uma única passagem usa tempo O(n) e espaço O(1). Para um vetor com 10⁵ postos, a diferença é de 10¹⁰ operações contra 10⁵. A principal propriedade matemática que permite a abordagem gulosa é: se o combustível líquido total for não negativo, existe um início válido, que será sempre o posto imediatamente depois do último ponto em que a soma acumulada ficou negativa.
def brute_force_gas(gas, cost):
n = len(gas)
for start in range(n):
tank = 0
valid = True
for i in range(n):
idx = (start + i) % n
tank += gas[idx] - cost[idx]
if tank < 0: valid = False; break
if valid: return start
return -1
def greedy_gas(gas, cost):
if sum(gas) < sum(cost): return -1
tank = start = 0
for i, (g, c) in enumerate(zip(gas, cost)):
tank += g - c
if tank < 0: tank = 0; start = i + 1
return start
gas = [1,2,3,4,5]; cost = [3,4,5,1,2]
print('Brute:', brute_force_gas(gas,cost), '== Greedy:', greedy_gas(gas,cost))Relacionado: Custo Mínimo para Completar Viagens
Tempo Mínimo para Completar Viagens (LeetCode 2187) é um problema de busca binária no espaço de respostas. Você faz uma busca binária sobre o valor de tempo T: dado o tempo T, os ônibus com time[i] completam floor(T/time[i]) viagens. Se o total de viagens ≥ totalTrips, T é suficiente. Encontre o menor T que satisfaça essa condição. Isso mostra que a estratégia gulosa pode ser aplicada no metanível (fazendo uma busca binária sobre as respostas) quando não existe uma regra gulosa direta no nível dos objetos.
def minimum_time(time, total_trips):
def can_complete(t):
return sum(t // bus for bus in time) >= total_trips
lo, hi = 1, min(time) * total_trips # upper bound
while lo < hi:
mid = (lo + hi) // 2
if can_complete(mid):
hi = mid
else:
lo = mid + 1
return lo
print(minimum_time([1, 2, 3], 5)) # 3 (3/1=3 + 3/2=1 + 3/3=1 = 5)
print(minimum_time([2], 1)) # 2Casos Extremos e Verificação
Casos extremos importantes para ambos os problemas: Agendador de Tarefas — quando o intervalo de espera n=0, a resposta é simplesmente o número de tarefas (não é necessário nenhum período ocioso). Quando todas as tarefas são iguais (por exemplo, todas são 'A'), os intervalos ociosos são preenchidos exatamente. Quando as tarefas têm muitos tipos distintos, pode haver 0 intervalos ociosos (as tarefas preenchem todos os quadros). Posto de Gasolina — quando a quantidade total de combustível é exatamente igual ao custo total, existe exatamente um início válido. Quando um único posto tem combustível suficiente para todo o circuito, esse posto é a resposta. Verifique sempre sua resposta obtida pelo algoritmo guloso nesses casos degenerados.
from collections import Counter
def least_interval(tasks, n):
if n == 0: return len(tasks) # no cooldown
cnt = Counter(tasks)
mf = max(cnt.values())
mc = sum(1 for c in cnt.values() if c == mf)
return max(len(tasks), (mf-1)*(n+1)+mc)
# Edge cases for task scheduler
print(least_interval(['A','A','A'], 2)) # 7: A _ _ A _ _ A
print(least_interval(['A','A','B','B'], 0)) # 4: no idle
print(least_interval(['A','B','C','D'], 3)) # 4: all diff, no idle needed
# Edge case for gas station
def gas_station(gas, cost):
if sum(gas) < sum(cost): return -1
tank = start = 0
for i,(g,c) in enumerate(zip(gas,cost)):
tank += g-c
if tank < 0: tank=0; start=i+1
return start
print(gas_station([5,1,2,3,4],[4,4,1,5,1])) # 4Reconhecimento de Padrões Gulosos
Tanto o Agendador de Tarefas quanto o Posto de Gasolina seguem o padrão guloso: (1) identifique o gargalo (a tarefa mais frequente / o saldo líquido de combustível); (2) tome uma decisão em uma única passagem usando uma variável acumulada (max_freq, tank); (3) reinicie ou redefina quando uma restrição for violada. Problemas gulosos comuns que você deve conhecer: Seleção de Atividades, Codificação de Huffman, Mochila Fracionária, Jogo de Saltos, Agendador de Tarefas, Posto de Gasolina e Mesclagem de Intervalos. Cada um tem uma prova por argumento de troca ou por um invariante matemático.
# Greedy pattern summary
# Task Scheduler:
# Bottleneck: max frequency task
# Formula: max(total_tasks, (max_freq-1)*(n+1)+max_count)
# O(n) time, O(1) space
# Gas Station:
# Bottleneck: running sum of (gas-cost) going negative
# Reset start when tank < 0, valid if total sum >= 0
# O(n) time, O(1) space
# Both avoid the need for DP by using a clever single-pass insight
from collections import Counter
def combined_demo(tasks, n, gas, cost):
ti = max(len(tasks), (max(Counter(tasks).values())-1)*(n+1) +
sum(1 for c in Counter(tasks).values() if c==max(Counter(tasks).values())))
tank = start = 0
gs = sum(g-c for g,c in zip(gas,cost)) >= 0
return ti, start if gs else -1Verificaçã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: resposta do Agendador de Tarefas = max(total_tasks, (max_freq-1)*(n+1)+max_count) — derivada do preenchimento de grades baseadas em quadros com a tarefa mais frequente, o Posto de Gasolina usa uma única passagem, redefinindo start=i+1 sempre que tank fica negativo, e é válido quando o combustível total ≥ o custo total e ambos os problemas usam tempo O(n) e espaço O(1), identificando um invariante matemático em vez de fazer uma busca exaustiva. A seguir, estudaremos o modelo de Divisão e Conquista e suas aplicações além da ordenação por intercalação.
Perguntas Frequentes
A aula “Escalonador de tarefas e posto de combustível” é grátis?
Sim — o texto completo de “Escalonador de tarefas e posto de combustível” é 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 DSA Interview Prep, atualize para CoddyKit PRO. O curso de DSA Interview Prep inclui 4 aulas no total.
O que vou aprender em “Escalonador de tarefas e posto de combustível”?
Aplique o raciocínio guloso ao problema do período de resfriamento do escalonador de tarefas da CPU e ao problema de viabilidade de postos de combustível em um circuito. Você pratica DSA 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 DSA Interview Prep?
Nenhuma experiência prévia é necessária. DSA 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 4 de 4.
Quanto tempo leva a aula “Escalonador de tarefas e posto de combustível”?
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 DSA Interview Prep?
Sim. Cada aula de DSA 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