Planificateur de tâches et station-service
Appliquez un raisonnement glouton au problème de la période de refroidissement du planificateur de tâches du CPU et à celui de la faisabilité d’un parcours circulaire de stations-service.
Planificateur de tâches et station-service est une leçon Coding Interview Prep gratuite sur CoddyKit. Ceci est la leçon 4 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Coding Interview Prep, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Coding Interview Prep comprend 4 leçons au total.
Problème de l’ordonnanceur de tâches
Ordonnanceur de tâches (LeetCode 621) : étant donné une liste de tâches du CPU, chacune étiquetée de A à Z, ainsi qu’un délai de refroidissement n, trouvez le nombre minimal d’intervalles du CPU nécessaires pour terminer toutes les tâches. Une même tâche doit attendre au moins n intervalles avant d’être exécutée de nouveau. Les intervalles d’inactivité sont autorisés. Pour les tâches ['A','A','A','B','B','B'] avec un délai de refroidissement de 2, la réponse est 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')Formule gloutonne pour l’ordonnanceur de tâches
Idée clé : la durée totale est déterminée par la tâche la plus fréquente. Si la tâche la plus fréquente apparaît f fois et si max_count représente le nombre de tâches ayant cette fréquence f, la durée est max(len(tasks), (f-1) * (n+1) + max_count). La formule consiste à créer f-1 blocs de taille n+1, à les remplir avec les autres tâches, puis à ajouter le dernier cycle. Si les autres tâches remplissent tous les créneaux d’inactivité, notamment lorsqu’il existe de nombreuses tâches différentes, exécutez simplement toutes les tâches sans temps d’inactivité.
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)) # 10Pourquoi la formule fonctionne
Imaginez l’ordonnancement comme une grille de n+1 colonnes, comprenant un créneau de tâche et n créneaux de refroidissement. La tâche A, qui est la plus fréquente, avec une fréquence f, nécessite f lignes. Entre la première et la dernière occurrence, il y a f-1 blocs complets de n+1 créneaux. Il faut ensuite ajouter le dernier bloc partiel, qui contient toutes les tâches ayant la fréquence maximale. S’il y a suffisamment de tâches différentes, elles remplissent tous les créneaux d’inactivité, et le nombre réel de tâches dépasse la durée des blocs — choisissez la plus grande des deux valeurs.
# 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)}')Alternative par simulation avec un tas
Une simulation fondée sur un tas produit l’ordonnancement réel, et pas seulement le nombre total. À chaque étape, prenez la tâche disponible la plus fréquente, à l’aide d’un tas-max. Après son exécution, appliquez le délai de refroidissement : ne la réinsérez qu’après n étapes. Utilisez une file pour suivre les tâches en cours de refroidissement. Cette solution s’exécute en O(total_time × log k), où k est le nombre de tâches distinctes. Bien qu’elle soit correcte, la formule est plus rapide. Connaissez les deux approches : les examinateurs peuvent vous demander de produire l’ordonnancement lui-même.
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)) # 8Problème de la station-service
Station-service (LeetCode 134) : il y a n stations-service disposées en cercle. La station i possède gas[i] unités de carburant, et le trajet jusqu’à la station suivante coûte cost[i] unités. En partant avec un réservoir vide, trouvez la station de départ depuis laquelle vous pouvez effectuer le circuit complet. Si aucune station ne convient, renvoyez -1. Le problème garantit qu’il existe au plus une réponse valide lorsqu’une telle réponse existe.
# 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 existsSolution gloutonne pour la station-service
Algorithme glouton : (1) Si le carburant total est inférieur au coût total, aucune solution n’existe (renvoyez -1). (2) Sinon, il existe exactement une solution. Trouvez-la en un seul parcours : suivez tank (le carburant actuel) et start (la station de départ candidate). Si tank < 0 après avoir visité une station, la station de départ actuelle ne peut pas atteindre cette station — réinitialisez tank = 0 et définissez start = i + 1. Le start final est la réponse.
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)) # -1Pourquoi le choix glouton du départ est correct
Argument de correction : si le réservoir devient négatif après avoir atteint la station i depuis start, aucune station située entre start et i, bornes comprises, ne peut être un point de départ valide — à leur arrivée à la station i, elles disposent toutes de moins de carburant que celui dont disposerait un départ depuis start. Nous pouvons donc ignorer toutes ces stations en toute sécurité et essayer i+1. Puisqu’une solution existe, car le carburant total est supérieur ou égal au coût total, la dernière station candidate start doit fonctionner.
# 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)) # TrueForce brute ou approche gloutonne pour la station-service
La force brute essaie chaque station de départ et simule le circuit complet — O(n²) en temps. La solution gloutonne en un seul parcours est en O(n) en temps et O(1) en espace. Pour un tableau de 10⁵ stations, la différence est de 10¹⁰ opérations contre 10⁵. La propriété mathématique clé qui permet l’approche gloutonne est la suivante : si le carburant net total est non négatif, un départ valide existe, et il s’agit toujours de la station située juste après le dernier point où la somme cumulée est devenue négative.
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))Associé : Temps minimal pour terminer les trajets
Temps minimal pour terminer les trajets (LeetCode 2187) est un problème de recherche binaire dans l’espace des réponses. Vous effectuez une recherche binaire sur la valeur du temps T : pour un temps T donné, les bus dont le temps est time[i] terminent floor(T/time[i]) trajets. Si le nombre total de trajets est ≥ totalTrips, T est suffisant. Il faut trouver le plus petit T répondant à cette condition. Cela montre que l’approche gloutonne peut s’appliquer au méta-niveau — en effectuant une recherche binaire parmi les réponses — lorsqu’aucune règle gloutonne directe n’existe au niveau des objets.
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)) # 2Cas limites et vérification
Cas limites importants pour les deux problèmes : Planificateur de tâches — lorsque le temps de refroidissement n=0, la réponse est simplement le nombre de tâches (aucune période d’inactivité n’est nécessaire). Lorsque toutes les tâches sont identiques, par exemple toutes « A », les périodes d’inactivité sont remplies exactement. Lorsque les tâches comportent de nombreux types distincts, le nombre de périodes d’inactivité peut être nul, car les tâches remplissent tous les créneaux. Station-service — lorsque la quantité totale de carburant est exactement égale au coût total, il existe une unique position de départ valide. Lorsqu’une seule station fournit suffisamment de carburant pour parcourir tout le circuit, cette station est la réponse. Vérifiez toujours votre réponse gloutonne sur ces cas dégénérés.
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])) # 4Reconnaître les schémas gloutons
Le Planificateur de tâches et la Station-service suivent tous deux le schéma glouton suivant : (1) identifier le goulot d’étranglement, c’est-à-dire la tâche la plus fréquente ou le bilan net de carburant ; (2) prendre une décision en un seul parcours à l’aide d’une variable mise à jour au fil du parcours, comme la fréquence maximale ou le contenu du réservoir ; (3) recommencer ou réinitialiser lorsqu’une contrainte est enfreinte. Problèmes gloutons courants à connaître : sélection d’activités, codage de Huffman, problème du sac à dos fractionnaire, jeu de sauts, Planificateur de tâches, Station-service et fusion d’intervalles. Chacun possède une preuve par argument d’échange ou par invariant mathématique.
# 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 -1Vérification rapide
Testez votre compréhension des concepts de structures de données et d’algorithmes — préparation aux entretiens de programmation — présentés dans cette leçon.
Récapitulatif de la leçon
Dans cette leçon, vous avez appris que la réponse du Planificateur de tâches = maximum(nombre total de tâches, (fréquence maximale-1)*(n+1)+nombre maximal) — une formule obtenue en remplissant des grilles de créneaux à partir de la tâche la plus fréquente, que la Station-service s’exécute en un seul parcours et réinitialise le départ à i+1 chaque fois que le réservoir devient négatif ; une solution existe lorsque le carburant total ≥ le coût total, et que les deux problèmes utilisent un temps O(n) et un espace O(1) en identifiant un invariant mathématique plutôt qu’en effectuant une recherche exhaustive. Nous allons maintenant étudier le modèle diviser pour régner et ses applications au-delà du tri par fusion.
Questions Fréquemment Posées
La leçon « Planificateur de tâches et station-service » est-elle gratuite ?
Oui — le texte complet de « Planificateur de tâches et station-service » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Coding Interview Prep, passe à CoddyKit PRO. Le cours Coding Interview Prep comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Planificateur de tâches et station-service » ?
Appliquez un raisonnement glouton au problème de la période de refroidissement du planificateur de tâches du CPU et à celui de la faisabilité d’un parcours circulaire de stations-service. Tu pratiques Coding Interview Prep avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer Coding Interview Prep ?
Aucune expérience préalable n'est requise. Coding Interview Prep sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 4 sur 4.
Combien de temps prend la leçon « Planificateur de tâches et station-service » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon Coding Interview Prep ?
Oui. Chaque leçon Coding Interview Prep inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Glouton ou DP : quand utiliser chaque approche
- Planification et fusion d’intervalles
- Jeu des sauts I et II
- Planificateur de tâches et station-service