Task Scheduler y Gas Station
Aplique razonamiento voraz al problema del periodo de enfriamiento del planificador de tareas de la CPU y al problema de viabilidad de una estación de servicio circular.
Task Scheduler y Gas Station es una lección gratuita de DSA Interview Prep en CoddyKit. Esta es la lección 4 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de DSA Interview Prep, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de DSA Interview Prep incluye 4 lecciones en total.
Problema del planificador de tareas
Planificador de tareas (LeetCode 621): dada una lista de tareas de CPU (cada una etiquetada de la A a la Z) y un periodo de enfriamiento n, encuentre el número mínimo de intervalos de CPU necesarios para finalizar todas las tareas. La misma tarea debe esperar al menos n intervalos antes de volver a ejecutarse. Se permiten intervalos inactivos. Para las tareas ['A','A','A','B','B','B'] con un periodo de enfriamiento 2, la respuesta es 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 greedy para el planificador de tareas
La idea clave es que el tiempo total viene determinado por la tarea más frecuente. Si la tarea más frecuente aparece f veces y max_count es el número de tareas con frecuencia f, el tiempo es max(len(tasks), (f-1) * (n+1) + max_count). La fórmula consiste en crear f-1 bloques de tamaño n+1, llenarlos con otras tareas y añadir el último ciclo. Si las demás tareas llenan todos los intervalos inactivos (hay muchas tareas distintas), simplemente ejecute todas las tareas sin tiempo inactivo.
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 qué funciona la fórmula
Visualice la planificación como una cuadrícula con n+1 columnas (una ranura para una tarea y n ranuras de enfriamiento). La tarea A, que es la más frecuente (frecuencia f), necesita f filas. Entre la primera y la última aparición hay f-1 bloques completos de n+1 ranuras. Además, hay un último bloque parcial que contiene todas las tareas con frecuencia máxima. Si hay suficientes tareas distintas, estas llenan todas las ranuras inactivas y el número real de tareas supera el tiempo de los bloques; tome el mayor de los dos valores.
# 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 simulación con heap
Una simulación basada en heap proporciona la planificación real, no solo el recuento. En cada paso, tome la tarea disponible con mayor frecuencia (max-heap). Después de ejecutarla, aplique el periodo de enfriamiento: no vuelva a insertarla hasta pasados n pasos. Utilice una cola para realizar el seguimiento de las tareas en enfriamiento. Esto se ejecuta en O(total_time × log k), donde k es el número de tareas distintas. Aunque es correcta, la fórmula es más rápida. Conozca ambas; los entrevistadores podrían pedirle la planificación en sí.
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 de la gasolinera
Gasolinera (LeetCode 134): hay n gasolineras dispuestas en un círculo. La estación i tiene gas[i] unidades de gasolina y viajar hasta la estación siguiente cuesta cost[i]. Comenzando con el depósito vacío, encuentre la estación de inicio desde la que puede completar el circuito. Si no existe ninguna, devuelva -1. El problema garantiza que existe como máximo una respuesta válida.
# 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 existsSolución greedy para la gasolinera
Algoritmo greedy: (1) si el combustible total < el coste total, no existe ninguna solución (devuelva -1). (2) De lo contrario, existe exactamente una solución. Encuéntrela con un solo recorrido: realice el seguimiento de tank (el combustible actual) y start (la estación de inicio candidata). Si tank < 0 después de visitar una estación, el start actual no puede llegar a esa estación; restablezca tank = 0 y establezca start = i + 1. El start final es la respuesta.
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 qué es correcto el inicio greedy
Argumento de corrección: si el tanque se vuelve negativo después de llegar a la estación i desde start, ninguna estación entre start e i (ambas incluidas) puede ser un punto de inicio válido; todas tienen menos combustible al llegar a la estación i que el que se tendría comenzando desde start. Por tanto, podemos omitirlas con seguridad y probar con i+1. Como existe una solución (el combustible total ≥ el coste total), el start candidato final debe 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)) # TrueFuerza bruta frente a greedy para la gasolinera
La fuerza bruta prueba cada estación de inicio y simula el circuito completo: O(n²) de tiempo. La solución greedy de un solo recorrido es O(n) de tiempo y O(1) de espacio. Para un array de 10⁵ estaciones, la diferencia es de 10¹⁰ operaciones frente a 10⁵. La propiedad matemática clave que permite el enfoque greedy es la siguiente: si el combustible neto total es no negativo, existe un inicio válido, que siempre es la estación situada justo después del último punto en el que la suma acumulada se volvió 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: coste mínimo para completar viajes
Tiempo mínimo para completar viajes (LeetCode 2187) es un problema de búsqueda binaria sobre el espacio de respuestas. Se realiza una búsqueda binaria sobre el valor del tiempo T: dado un tiempo T, los autobuses con time[i] completan floor(T/time[i]) viajes. Si el total de viajes ≥ totalTrips, T es suficiente. Hay que encontrar el menor T que cumpla esta condición. Esto demuestra que la estrategia greedy puede aplicarse en el metanivel (buscando respuestas mediante búsqueda binaria) cuando no existe una regla greedy directa en el nivel de los 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 límite y verificación
Casos límite importantes para ambos problemas: Task Scheduler: cuando n=0, la respuesta es simplemente len(tasks) (no se necesita tiempo de espera). Cuando todas las tareas son iguales (por ejemplo, todas son 'A'), los intervalos de espera se llenan exactamente. Cuando hay muchos tipos de tareas distintos, puede que no haya intervalos de espera (las tareas llenan todos los marcos). Gas Station: cuando el total de gasolina es exactamente igual al coste total, existe exactamente un punto de inicio válido. Cuando una sola estación tiene suficiente gasolina para completar todo el circuito, esa estación es la respuesta. Compruebe siempre su respuesta greedy con estos 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])) # 4Reconocimiento de patrones greedy
Tanto Task Scheduler como Gas Station siguen el patrón greedy: (1) Identificar el cuello de botella (la tarea más frecuente o el balance neto de combustible). (2) Tomar una decisión en una sola pasada mediante una variable acumulada (max_freq, tank). (3) Reiniciar o restablecer el estado cuando se incumple una restricción. Problemas greedy habituales que conviene conocer: selección de actividades, codificación de Huffman, mochila fraccionaria, Jump Game, Task Scheduler, Gas Station y Merge Intervals. Cada uno tiene una demostración basada en un argumento de intercambio o en un 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 -1Comprobación rápida
Compruebe su comprensión de los conceptos de Data Structures & Algorithms — Coding Interview Prep de esta lección.
Resumen de la lección
En esta lección aprendió: respuesta de Task Scheduler = max(total_tasks, (max_freq-1)*(n+1)+max_count), derivada de llenar cuadrículas basadas en marcos con la tarea más frecuente; Gas Station utiliza una sola pasada y restablece start=i+1 cada vez que tank se vuelve negativo; es válido cuando el total de gasolina ≥ el coste total; y ambos problemas utilizan tiempo O(n) y espacio O(1) al identificar un invariante matemático en lugar de realizar una búsqueda exhaustiva. A continuación estudiaremos la plantilla de divide y vencerás y sus aplicaciones más allá de merge sort.
Preguntas frecuentes
¿La lección «Task Scheduler y Gas Station» es gratis?
Sí — el texto completo de «Task Scheduler y Gas Station» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de DSA Interview Prep, actualiza a CoddyKit PRO. El curso de DSA Interview Prep incluye 4 lecciones en total.
¿Qué aprenderé en «Task Scheduler y Gas Station»?
Aplique razonamiento voraz al problema del periodo de enfriamiento del planificador de tareas de la CPU y al problema de viabilidad de una estación de servicio circular. Practicas DSA Interview Prep con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar DSA Interview Prep?
No se requiere experiencia previa. DSA Interview Prep en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 4 de 4.
¿Cuánto tiempo toma la lección «Task Scheduler y Gas Station»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de DSA Interview Prep?
Sí. Cada lección de DSA Interview Prep incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Voraces frente a PD: cuándo usar cada enfoque
- Planificación y fusión de intervalos
- Jump Game I y II
- Task Scheduler y Gas Station