Forberedelse til kodeinterviews · Lektion

Task Scheduler og Gas Station

Anvend grådig problemløsning på problemet med CPU-opgaver og afkølingsperioder samt problemet om gennemførlighed ved en cirkulær tankstation.

Lektion 4 af 413 trin

Task Scheduler og Gas Station er en gratis Forberedelse til kodeinterviews-lektion på CoddyKit. Dette er lektion 4 af 4. Du kan læse hele lektionen gratis nedenfor — og derefter øve dig praktisk i browseren med en indbygget kodeeditor og en AI-vejleder, der er tilgængelig døgnet rundt. Den er en del af læringsforløbet i Forberedelse til kodeinterviews, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Forberedelse til kodeinterviews-kurset indeholder 4 lektioner i alt.

Task Scheduler-problemet

Task Scheduler (LeetCode 621): Du får en liste over CPU-opgaver (hver mærket A-Z) og en afkølingsperiode n. Find det mindste antal CPU-intervaller, der kræves for at udføre alle opgaver. Den samme opgave skal vente mindst n intervaller, før den kan køres igen. Ledige intervaller er tilladt. For opgaverne ['A','A','A','B','B','B'] med en afkølingsperiode på 2 er 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')

Grådig formel til Task Scheduler

Den vigtigste pointe er, at den samlede tid bestemmes af den opgave, der forekommer oftest. Hvis den hyppigste opgave forekommer f gange, og der er max_count opgaver med frekvens f, er tiden max(len(tasks), (f-1) * (n+1) + max_count). Formlen opretter f-1 blokke af størrelsen n+1, fylder dem med andre opgaver og tilføjer den sidste cyklus. Hvis andre opgaver udfylder alle ledige pladser (mange forskellige opgaver), udføres alle opgaver blot uden ventetid.

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))  # 10

Hvorfor formlen virker

Visualisér tidsplanen som et gitter med n+1 kolonner (én opgaveplads og n afkølingspladser). Den hyppigste opgave A (med frekvens f) kræver f rækker. Mellem den første og sidste forekomst er der f-1 komplette blokke med n+1 pladser. Dertil kommer den sidste delvise blok, som indeholder alle opgaver med den maksimale frekvens. Hvis der er tilstrækkeligt mange forskellige opgaver, fylder de alle ledige pladser, og det faktiske opgaveantal overstiger blokkens tidsforbrug — vælg det største af de to.

# 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)}')

Alternativ: simulering med heap

En heap-baseret simulering giver den faktiske tidsplan, ikke kun antallet. Ved hvert trin vælges den hyppigste tilgængelige opgave fra en max-heap. Efter udførelsen anvendes afkøling: Opgaven må først indsættes igen n trin senere. Brug en kø til at holde styr på opgaver, der afkøles. Dette bruger O(total_time × log k) tid, hvor k er antallet af forskellige opgaver. Selvom metoden er korrekt, er formlen hurtigere. Kend begge metoder — interviewere kan bede om selve tidsplanen.

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))  # 8

Gas Station-problemet

Gas Station (LeetCode 134): Der er n tankstationer i en cirkel. Station i har gas[i] brændstof, og det koster cost[i] at køre til den næste station. Start med en tom tank, og find den station, hvorfra du kan gennemføre rundturen. Hvis der ikke findes en sådan station, returnér -1. Problemet garanterer, at der højst findes én gyldig løsning, hvis der findes en.

# 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 exists

Grådig løsning til Gas Station

Grådig algoritme: (1) Hvis den samlede mængde brændstof er mindre end de samlede omkostninger, findes der ingen løsning (returnér -1). (2) Ellers findes der præcis én løsning. Find den med ét gennemløb: Følg tank (det aktuelle brændstof) og start (kandidaten til startstation). Hvis tank < 0 efter et besøg på en station, kan det aktuelle start ikke nå denne station — nulstil tank = 0, og sæt start = i + 1. Det endelige start er 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))  # -1

Hvorfor den grådige start er korrekt

Korrekthedsargument: Hvis tanken bliver negativ, efter at du fra start har nået station i, kan ingen station mellem start og i (inklusive begge) være et gyldigt startpunkt — de har alle mindre brændstof, når de når station i, end en start fra start ville have. Derfor kan vi trygt springe dem alle over og prøve i+1. Da der findes en løsning (den samlede mængde brændstof ≥ de samlede omkostninger), skal den endelige kandidat start virke.

# 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))  # True

Udtømmende søgning kontra grådig metode til Gas Station

Udtømmende søgning prøver hver startstation og simulerer hele rundturen — O(n²) tid. Den grådige løsning med ét gennemløb bruger O(n) tid og O(1) plads. For et array med 10⁵ stationer er forskellen 10¹⁰ operationer mod 10⁵. Den afgørende matematiske egenskab, der gør den grådige metode mulig, er: Hvis den samlede nettomængde brændstof er ikke-negativ, findes der et gyldigt startpunkt, og det er altid stationen lige efter det sidste punkt, hvor den løbende sum 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))

Relateret: Minimumsomkostning for at gennemføre ture

Minimumstid for at gennemføre ture (LeetCode 2187) er et problem med binær søgning i svarrummet. Du laver binær søgning på tidsværdien T: For en given tid T gennemfører busser med time[i] floor(T/time[i]) ture. Hvis det samlede antal ture ≥ totalTrips, er T tilstrækkelig. Find den mindste værdi af T. Dette viser, at grådighed kan anvendes på metaniveauet (ved at lave binær søgning blandt svar), når der ikke findes en direkte grådig regel på objektniveauet.

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))          # 2

Grænsetilfælde og verifikation

Vigtige grænsetilfælde for begge problemer: Opgaveplanlæggeren — når nedkøling n=0, er svaret ganske enkelt len(tasks) (der er ikke brug for tomgang). Når alle opgaver er ens (f.eks. alle 'A'), udfyldes de ledige pladser præcist. Når opgaverne har mange forskellige typer, kan antallet af ledige pladser være 0 (opgaverne udfylder alle rammer). Benzinstationen — når den samlede mængde brændstof er nøjagtigt lig med de samlede omkostninger, findes der præcis én gyldig start. Når en enkelt station har nok brændstof til hele ruten, er denne station svaret. Kontrollér altid dit grådige svar på disse degenererede tilfælde.

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]))  # 4

Genkendelse af grådige mønstre

Både Opgaveplanlæggeren og Benzinstationen følger det grådige mønster: (1) Identificér flaskehalsen (den hyppigst forekommende opgave / nettobalancen for brændstof). (2) Træf en beslutning i én gennemkørsel med en løbende variabel (max_freq, tank). (3) Start forfra eller nulstil, når en begrænsning overtrædes. Almindelige grådige problemer, du bør kende: aktivitetsudvælgelse, Huffman-kodning, fraktioneret rygsæk, spring-spil, Opgaveplanlæggeren, Benzinstationen og fletning af intervaller. Hvert af dem har et bevis via et ombytningsargument 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 -1

Hurtig test

Test din forståelse af begreberne fra lektionen i Data Structures & Algorithms — Coding Interview Prep.

Opsummering af lektionen

I denne lektion lærte du: Svar for Opgaveplanlæggeren = max(total_tasks, (max_freq-1)*(n+1)+max_count) — udledt ved at udfylde gittere baseret på rammer med den hyppigst forekommende opgave, Benzinstationen bruger én gennemkørsel og nulstiller start=i+1, hver gang tank går under nul; løsningen er gyldig, når den samlede mængde brændstof ≥ de samlede omkostninger, og begge problemer bruger O(n)-tid og O(1)-plads ved at identificere en matematisk invariant i stedet for at søge udtømmende. Som det næste ser vi på skabelonen del og hersk og dens anvendelser ud over fletningssortering.

Gratis at komme i gang

Lær Forberedelse til kodeinterviews med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
90
Lektioner
360

Ofte stillede spørgsmål

Er lektionen “Task Scheduler og Gas Station” gratis?

Ja — hele teksten til “Task Scheduler og Gas Station” kan læses gratis her på nettet. Hvis du vil øve dig interaktivt med en indbygget kodeeditor og en AI-vejleder døgnet rundt og få adgang til resten af Forberedelse til kodeinterviews-kurset, skal du opgradere til CoddyKit PRO. Forberedelse til kodeinterviews-kurset indeholder 4 lektioner i alt.

Hvad lærer jeg i “Task Scheduler og Gas Station”?

Anvend grådig problemløsning på problemet med CPU-opgaver og afkølingsperioder samt problemet om gennemførlighed ved en cirkulær tankstation. Du øver dig i Forberedelse til kodeinterviews med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på Forberedelse til kodeinterviews?

Der kræves ingen tidligere erfaring. Forberedelse til kodeinterviews på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 4 af 4.

Hvor lang tid tager lektionen “Task Scheduler og Gas Station”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne Forberedelse til kodeinterviews-lektion?

Ja. Alle Forberedelse til kodeinterviews-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. Grådige algoritmer eller DP: Hvornår bruges hvad
  2. Intervalplanlægning og sammenfletning
  3. Jump Game I og II
  4. Task Scheduler og Gas Station
← Tilbage til Forberedelse til kodeinterviews