Forberedelse til kodeintervjuer · leksjon

Task Scheduler og Gas Station

Bruk grådig tankegang på problemet med nedkjølingsperioder i CPU-oppgaveplanlegging og problemet med gjennomførbarhet for en sirkulær bensinstasjonstur.

Leksjon 4 av 413 trinn

Task Scheduler og Gas Station er en gratis leksjon i Forberedelse til kodeintervjuer på CoddyKit. Dette er leksjon 4 av 4. Du kan lese hele leksjonen gratis nedenfor – og deretter øve praktisk i nettleseren med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i Forberedelse til kodeintervjuer, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i Forberedelse til kodeintervjuer inneholder totalt 4 leksjoner.

Task Scheduler-problemet

Task Scheduler (LeetCode 621): Det er gitt en liste med CPU-oppgaver (hver merket A–Z) og en cooldown n. Finn det minste antallet CPU-intervaller som trengs for å fullføre alle oppgavene. Den samme oppgaven må vente i minst n intervaller før den kan kjøres igjen. Ledige intervaller er tillatt. For oppgavene ['A','A','A','B','B','B'] med cooldown 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 for Task Scheduler

Hovedinnsikten er at den totale tiden bestemmes av oppgaven med høyest frekvens. Hvis oppgaven med høyest frekvens forekommer f ganger, og max_count er antallet oppgaver med frekvens f, er tiden max(len(tasks), (f-1) * (n+1) + max_count). Formelen lager f-1 rammer med størrelse n+1, fyller dem med andre oppgaver og legger til den siste syklusen. Hvis andre oppgaver fyller alle ledige plasser (mange ulike oppgaver), kjøres alle oppgavene ganske enkelt uten ledige intervaller.

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 formelen fungerer

Visualiser planen som et rutenett med n+1 kolonner (én oppgaveplass og n nedkjølingsplasser). Oppgaven A med høyest frekvens (frekvens f) trenger f rader. Mellom den første og siste forekomsten finnes f-1 komplette rammer med n+1 plasser. I tillegg kommer den siste delvise rammen, som inneholder alle oppgaver med maksimal frekvens. Hvis det finnes nok ulike oppgaver, fyller de alle ledige plasser, og det faktiske oppgaveantallet blir større enn rammetiden — velg det største av 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-basert simulering gir selve planen (ikke bare antallet). I hvert trinn velges den tilgjengelige oppgaven med høyest frekvens (max-heap). Etter kjøringen brukes cooldown: oppgaven settes ikke inn igjen før n trinn senere. Bruk en kø til å holde oversikt over oppgaver som er under nedkjøling. Dette kjører på O(total_time × log k), der k er antallet ulike oppgaver. Selv om løsningen er korrekt, er formelen raskere. Kjenn til begge — intervjuere kan be om selve planen.

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): Det finnes n bensinstasjoner i en sirkel. Stasjon i har gas[i] drivstoff, og det koster cost[i] å kjøre til neste stasjon. Start med tom tank og finn stasjonen som gjør det mulig å fullføre runden. Hvis ingen slik stasjon finnes, returner -1. Problemet garanterer at det finnes høyst ett gyldig svar, dersom et svar finnes.

# 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 for Gas Station

Grådig algoritme: (1) Hvis total mengde drivstoff < total kostnad, finnes det ingen løsning (returner -1). (2) Ellers finnes det nøyaktig én løsning. Finn den med én gjennomgang: følg med på tank (gjeldende drivstoff) og start (kandidatstasjonen). Hvis tank < 0 etter besøket på en stasjon, kan den gjeldende start ikke nå denne stasjonen — tilbakestill tank = 0 og sett start = i + 1. Den endelige verdien av 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 startposisjonen er korrekt

Korrekthetsargument: Hvis tanken blir negativ etter at stasjon i er nådd fra start, kan ingen stasjon mellom start og i (inkludert disse) være et gyldig startpunkt — de har alle mindre drivstoff når de når stasjon i, enn det man ville hatt ved å starte fra start. Derfor kan alle hoppes over på en trygg måte, og gjennomgangen kan fortsette fra i+1. Siden det finnes en løsning (total mengde drivstoff ≥ total kostnad), må den endelige kandidaten start fungere.

# 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

Brute force versus grådig løsning for Gas Station

Brute force prøver hver startstasjon og simulerer hele runden — O(n²) tid. Den grådige løsningen med én gjennomgang bruker O(n) tid og O(1) plass. For en tabell med 10⁵ stasjoner er forskjellen 10¹⁰ operasjoner mot 10⁵. Den sentrale matematiske egenskapen som gjør den grådige løsningen mulig, er at hvis den totale nettomengden av drivstoff er ikke-negativ, finnes det et gyldig startpunkt, og det er alltid stasjonen rett etter det siste punktet der den løpende summen ble 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))

Relatert: Minimum Cost to Complete Trips

Minimum Time to Complete Trips (LeetCode 2187) er et binærsøkproblem i svarrommet. Her utføres binærsøk på tidsverdien T: Gitt tiden T fullfører busser med time[i] floor(T/time[i]) turer. Hvis det totale antallet turer ≥ totalTrips, er T tilstrekkelig. Finn den minste slike T. Dette viser at grådighet kan brukes på metanivået (ved å utføre binærsøk over svarene) når det ikke finnes noen direkte grådig regel på objektnivået.

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

Kanttilfeller og verifisering

Viktige kanttilfeller for begge problemene: Task Scheduler — når cooldown n=0, er svaret ganske enkelt len(tasks) (ingen tomgang er nødvendig). Når alle oppgavene er like (for eksempel alle 'A'), fylles tomgangsplassene nøyaktig. Når oppgavene har mange forskjellige typer, kan antallet tomgangsplasser være 0 (oppgavene fyller alle tidsrammene). Gas Station — når den totale mengden drivstoff er nøyaktig lik den totale kostnaden, finnes det nøyaktig én gyldig start. Når én stasjon har nok drivstoff til hele kretsløpet, er denne stasjonen svaret. Kontroller alltid det grådige svaret på disse degenererte tilfellene.

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

Gjenkjenning av grådige mønstre

Både Task Scheduler og Gas Station følger det grådige mønsteret: (1) Identifiser flaskehalsen (den hyppigste oppgaven / nettobalansen for drivstoff). (2) Ta en beslutning i én gjennomgang med en løpende variabel (max_freq, tank). (3) Start på nytt eller tilbakestill når en begrensning brytes. Vanlige grådige problemer det er nyttig å kjenne til, er Activity Selection, Huffman Coding, Fractional Knapsack, Jump Game, Task Scheduler, Gas Station og Merge Intervals. Alle har et bevis ved hjelp av et bytteargument 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

Kort kontroll

Test forståelsen av konseptene innen Data Structures & Algorithms — Coding Interview Prep fra denne leksjonen.

Oppsummering av leksjonen

I denne leksjonen lærte De: Svaret for Task Scheduler = max(total_tasks, (max_freq-1)*(n+1)+max_count) — utledet ved å fylle rutenett basert på tidsrammer med den hyppigste oppgaven, Gas Station bruker én gjennomgang og tilbakestiller start=i+1 hver gang tank blir negativ; løsningen er gyldig når den totale mengden drivstoff ≥ den totale kostnaden, og begge problemene bruker O(n) tid og O(1) plass ved å identifisere en matematisk invariant i stedet for å søke uttømmende. Deretter ser vi på malen for Divide and Conquer og bruksområdene dens utover merge sort.

Gratis å komme i gang

Lær deg Forberedelse til kodeintervjuer med en AI-veileder – gratis

Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.

Kurs
90
Leksjoner
360

Ofte stilte spørsmål

Er leksjonen «Task Scheduler og Gas Station» gratis?

Ja – hele teksten i «Task Scheduler og Gas Station» er gratis å lese her på nettet. For å øve interaktivt med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt, og for å låse opp resten av Forberedelse til kodeintervjuer-kurset, kan du oppgradere til CoddyKit PRO. Kurset i Forberedelse til kodeintervjuer inneholder totalt 4 leksjoner.

Hva lærer jeg i «Task Scheduler og Gas Station»?

Bruk grådig tankegang på problemet med nedkjølingsperioder i CPU-oppgaveplanlegging og problemet med gjennomførbarhet for en sirkulær bensinstasjonstur. Du øver på Forberedelse til kodeintervjuer med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.

Trenger jeg erfaring for å begynne med Forberedelse til kodeintervjuer?

Ingen tidligere erfaring er nødvendig. Forberedelse til kodeintervjuer på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 4 av 4.

Hvor lang tid tar leksjonen «Task Scheduler og Gas Station»?

De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.

Kan jeg skrive og kjøre kode i denne Forberedelse til kodeintervjuer-leksjonen?

Ja. Alle Forberedelse til kodeintervjuer-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.

Alle leksjonene i dette kurset

  1. Grådig algoritme eller DP: Når skal du bruke hva
  2. Intervallplanlegging og sammenslåing
  3. Jump Game I og II
  4. Task Scheduler og Gas Station
← Tilbake til Forberedelse til kodeintervjuer