Task Scheduler och Gas Station
Tillämpa girigt resonemang på problemet med nedkylningsperioder i CPU:s task scheduler och på problemet att avgöra genomförbarheten för en cirkulär bensinstationsrutt.
Task Scheduler och Gas Station är en gratis lektion i DSA Interview Prep på CoddyKit. Detta är lektion 4 av 4. Du kan läsa vilka 3 lektioner som helst i den här lärvägen kostnadsfritt i sin helhet – därefter låser CoddyKit PRO upp alla lektioner, plus praktisk övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Den ingår i lärvägen för DSA Interview Prep, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i DSA Interview Prep innehåller totalt 4 lektioner.
Task Scheduler-problemet
Task Scheduler (LeetCode 621): givet en lista med CPU-uppgifter (var och en märkt A–Z) och en cooldown n, hitta det minsta antalet CPU-intervall som krävs för att slutföra alla uppgifter. Samma uppgift måste vänta minst n intervall innan den körs igen. Overksamma intervall är tillåtna. För uppgifterna ['A','A','A','B','B','B'] med cooldown 2 är svaret 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')Girig formel för Task Scheduler
Den viktigaste insikten är att den totala tiden bestäms av uppgiften med högst frekvens. Om den uppgiften förekommer f gånger och det finns max_count uppgifter med frekvens f, blir tiden max(len(tasks), (f-1) * (n+1) + max_count). Formeln skapar f-1 block med storleken n+1, fyller dem med andra uppgifter och lägger till den sista cykeln. Om andra uppgifter fyller alla lediga platser (många olika uppgifter), körs helt enkelt alla uppgifter utan några overksamma intervall.
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)) # 10Varför formeln fungerar
Visualisera schemat som ett rutnät med n+1 kolumner (en uppgiftsplats + n cooldown-platser). Den uppgift som förekommer oftast, A (frekvens f), behöver f rader. Mellan den första och sista förekomsten finns f-1 fullständiga block med n+1 platser. Dessutom tillkommer det sista ofullständiga blocket som innehåller alla uppgifter med maximal frekvens. Om det finns tillräckligt många olika uppgifter fyller de alla lediga platser, och det faktiska antalet uppgifter överstiger blocktiden — använd det större av de två värdena.
# 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)}')Heap-baserat simuleringsalternativ
En heap-baserad simulering ger det faktiska schemat (inte bara antalet). I varje steg väljer ni den tillgängliga uppgift som förekommer oftast (max-heap). Efter körningen tillämpas cooldown: uppgiften får inte läggas tillbaka förrän n steg senare. Använd en kö för att hålla reda på uppgifter som kyls ned. Detta körs på O(total_time × log k), där k är antalet olika uppgifter. Metoden är korrekt, men formeln är snabbare. Det är bra att känna till båda — intervjuare kan be er ta fram själva schemat.
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)) # 8Gas Station-problemet
Gas Station (LeetCode 134): det finns n bensinstationer längs en cirkulär rutt. Station i har gas[i] bränsle och det kostar cost[i] att köra till nästa station. Börja med en tom tank och hitta den startstation från vilken ni kan köra hela varvet. Om ingen sådan station finns, returnera -1. Problemet garanterar att det finns högst ett giltigt svar om ett sådant finns.
# 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 existsGirig lösning för Gas Station
Girig algoritm: (1) Om den totala mängden bränsle < den totala kostnaden finns ingen lösning (returnera -1). (2) Annars finns det exakt en lösning. Hitta den genom en enda genomgång: håll reda på tank (aktuellt bränsle) och start (kandidat för startstation). Om tank < 0 efter att ni har besökt en station kan den aktuella start inte nå den stationen — återställ tank = 0 och sätt start = i + 1. Det slutliga värdet på start är svaret.
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)) # -1Varför den giriga starten är korrekt
Korrekthetsargument: om tanken blir negativ efter att ni har nått station i från start, kan ingen station mellan start och i (inklusive dessa) vara en giltig startpunkt — de skulle alla ha mindre bränsle när de når station i än en start från start skulle ge. Därför kan vi tryggt hoppa över dem och försöka med i+1. Eftersom det finns en lösning (den totala mängden bränsle ≥ den totala kostnaden) måste den slutliga kandidaten start fungera.
# 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)) # TrueBrute force jämfört med girig metod för Gas Station
Brute force testar varje startstation och simulerar hela varvet — O(n²) tid. Den giriga lösningen med en enda genomgång tar O(n) tid och O(1) minnesutrymme. För en array med 10⁵ stationer är skillnaden 10¹⁰ operationer jämfört med 10⁵. Den matematiska egenskap som gör den giriga metoden möjlig är att det finns en giltig start om den totala nettomängden bränsle är icke-negativ, och att den alltid är stationen direkt efter den sista punkten där den löpande summan blev negativ.
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))Relaterat: Minimum Cost to Complete Trips
Minimum Time to Complete Trips (LeetCode 2187) är ett problem med binärsökning över svarsrummet. Man gör en binärsökning på tidsvärdet T: vid tiden T hinner bussar med time[i] slutföra floor(T/time[i]) resor. Om det totala antalet resor ≥ totalTrips räcker T. Hitta det minsta sådana T. Detta visar att girighet kan tillämpas på metanivå (genom att binärsöka bland svaren) när det inte finns någon direkt girig regel på objektnivå.
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)) # 2Randfall och verifiering
Viktiga randfall för båda problemen: Task Scheduler — när nedkylningsintervallet n=0 är svaret helt enkelt len(tasks) (ingen inaktiv tid behövs). När alla uppgifter är samma (t.ex. alla 'A') fylls de tomma platserna exakt. När uppgifterna har många olika typer kan antalet tomma platser vara 0 (uppgifterna fyller alla tidsramar). Gas Station — när den totala mängden bensin exakt motsvarar den totala kostnaden finns exakt en giltig startpunkt. När en enskild station har tillräckligt med bensin för hela rundan är den stationen svaret. Verifiera alltid det giriga svaret med hjälp av dessa degenererade fall.
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])) # 4Mönsterigenkänning för giriga algoritmer
Både Task Scheduler och Gas Station följer det giriga mönstret: (1) Identifiera flaskhalsen (den vanligaste uppgiften / nettobalansen för bränsle). (2) Fatta beslut i ett enda genomlopp med en variabel som uppdateras löpande (max_freq, tank). (3) Starta om eller återställ när en begränsning bryts. Vanliga giriga problem att känna till är: Activity Selection, Huffman Coding, Fractional Knapsack, Jump Game, Task Scheduler, Gas Station och Merge Intervals. Var och en har ett bevis med hjälp av ett utbytesargument eller en matematisk invariant.
# 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 -1Snabbtest
Testa era kunskaper om begreppen Data Structures & Algorithms — Coding Interview Prep från den här lektionen.
Sammanfattning av lektionen
I den här lektionen har ni lärt er: svaret för Task Scheduler = max(total_tasks, (max_freq-1)*(n+1)+max_count) — härlett genom att fylla rutnät med tidsramar med den vanligaste uppgiften, Gas Station använder ett enda genomlopp och återställer start=i+1 varje gång tank blir negativ, och är giltigt när total gas ≥ total cost, samt båda problemen använder O(n) tid och O(1) utrymme genom att identifiera en matematisk invariant i stället för att göra en uttömmande sökning. Nästa del handlar om mallen Divide and Conquer och dess tillämpningar bortom mergesort.
Lär dig Python med en AI-lärare – gratis
Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.
- Kurser
- 30
- Lektioner
- 120
Vanliga frågor
Är lektionen ”Task Scheduler och Gas Station” gratis?
Ja – du kan läsa vilka 3 lektioner som helst i lärvägen DSA Interview Prep, inklusive ”Task Scheduler och Gas Station”, kostnadsfritt i sin helhet här på webben. Därefter låser CoddyKit PRO upp alla lektioner, plus interaktiv övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Kursen i DSA Interview Prep innehåller totalt 4 lektioner.
Vad lär jag mig i ”Task Scheduler och Gas Station”?
Tillämpa girigt resonemang på problemet med nedkylningsperioder i CPU:s task scheduler och på problemet att avgöra genomförbarheten för en cirkulär bensinstationsrutt. Ni övar på DSA Interview Prep med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.
Behöver jag någon erfarenhet för att börja lära mig DSA Interview Prep?
Du behöver inga förkunskaper. Utbildningen i DSA Interview Prep på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 4 av 4.
Hur lång tid tar lektionen ”Task Scheduler och Gas Station”?
De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.
Kan jag skriva och köra kod i den här DSA Interview Prep-lektionen?
Ja. Varje DSA Interview Prep-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.
Alla lektioner i den här kursen
- Giriga algoritmer eller DP: när används vad
- Intervallschemaläggning och sammanslagning
- Jump Game I och II
- Task Scheduler och Gas Station