Forberedelse til kodeinterviews · Lektion

Ubegrænset knapsack og Coin Change II

Tillad genbrug af elementer ved at gennemløbe kapaciteten forlæns, og løs coin-change-II (optælling af måder) samt rod-cutting med denne variant.

Lektion 2 af 413 trin

Ubegrænset knapsack og Coin Change II er en gratis Forberedelse til kodeinterviews-lektion på CoddyKit. Dette er lektion 2 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.

Konceptet bag den ubegrænsede rygsæk

I den ubegrænsede rygsæk kan hvert element vælges et vilkårligt antal gange (i modsætning til 0/1-rygsækken, hvor hvert element højst bruges én gang). Definitionen af tilstanden er den samme — dp[c] = den maksimale værdi, der kan opnås med kapaciteten c — men gennemløbsretningen ændres. Fordi elementerne kan genbruges, vil vi tillade, at det aktuelle element bruges igen, når vi opdaterer dp[c], så vi gennemløber kapaciteten fra venstre mod højre (fremad).

Fremadrettet gennemløb muliggør genbrug

Husk, at vi i 0/1-rygsækken gennemløb fra højre mod venstre for at forhindre genbrug. I den ubegrænsede rygsæk gør vi det modsatte: Vi gennemløber fra venstre mod højre. Når vi beregner dp[c], er dp[c-w] allerede blevet opdateret i det aktuelle gennemløb — det betyder, at element i muligvis allerede er taget med. Det er præcis det, vi ønsker: Element i kan føjes til igen i en løsning, der allerede indeholder element i.

def unbounded_knapsack(weights, values, W):
    dp = [0] * (W + 1)
    
    for i in range(len(weights)):
        w, v = weights[i], values[i]
        for c in range(w, W + 1):  # iterate LEFT TO RIGHT
            dp[c] = max(dp[c], dp[c - w] + v)
    
    return dp[W]

weights = [1, 3, 4, 5]
values  = [1, 4, 5, 7]
print(unbounded_knapsack(weights, values, 7))  # 9

Coin Change II: Tæl antal måder

Coin Change II spørger: Givet møntværdier og et beløb, hvor mange forskellige måder er der til at danne beløbet (hver mønt kan bruges et ubegrænset antal gange)? Dette er en variant af den ubegrænsede rygsæk, hvor vi i stedet for at maksimere en værdi tæller kombinationer. Definér dp[c] som antallet af måder at danne beløbet c på. Basistilfælde: dp[0] = 1 (én måde at danne 0 på: vælg ingenting).

Implementering af Coin Change II

For hver mønt gennemløber du beløbene fra venstre mod højre og akkumulerer: dp[c] += dp[c - coin]. Basistilfældet dp[0] = 1 starter optællingen. Bemærk, at den ydre løkke går over mønter, mens den indre løkke går over beløb — det giver naturligt kombinationstællinger (ikke permutationer), fordi hver møntværdi behandles præcis én gang i den ydre gennemløb.

def change(amount, coins):
    dp = [0] * (amount + 1)
    dp[0] = 1  # one way to make amount 0
    
    for coin in coins:
        for c in range(coin, amount + 1):
            dp[c] += dp[c - coin]
    
    return dp[amount]

print(change(5, [1, 2, 5]))   # 4
print(change(3, [2]))          # 0
print(change(10, [10]))        # 1

Kombinationer kontra permutationer

Løkkernes rækkefølge er afgørende. Hvis vi placerer beløbet i den ydre løkke og mønten i den indre løkke, tæller vi permutationer (rækkefølgen betyder noget). For amount=5 med coins [1,2] tælles 1+2+2 og 2+1+2 separat. Hvis vi placerer mønten i den ydre løkke, tæller vi kombinationer (rækkefølgen betyder ikke noget): 1+2+2 og 2+1+2 er det samme. Coin Change II spørger efter kombinationer, så mønten er i den ydre løkke.

# Count COMBINATIONS (order does not matter) — coin outer loop
def combinations(amount, coins):
    dp = [0] * (amount + 1)
    dp[0] = 1
    for coin in coins:          # coin outer
        for c in range(coin, amount + 1):
            dp[c] += dp[c - coin]
    return dp[amount]

# Count PERMUTATIONS (order matters) — amount outer loop
def permutations(amount, coins):
    dp = [0] * (amount + 1)
    dp[0] = 1
    for c in range(1, amount + 1):  # amount outer
        for coin in coins:
            if c >= coin:
                dp[c] += dp[c - coin]
    return dp[amount]

print(combinations(5, [1,2,5]))   # 4
print(permutations(5, [1,2,5]))   # 13

Problemet med stangskæring

Et andet klassisk problem med den ubegrænsede rygsæk: Givet en stang med længden n og priser for hver stanglængde fra 1 til n skal du finde den maksimale indtjening ved at skære stangen optimalt. Hvert stykke med længden l kan sælges for price[l], og stykker kan genbruges (stangen kan skæres i flere stykker med samme længde). Det svarer direkte til den ubegrænsede rygsæk med W = n, hvor elementerne er de forskellige skærelængder.

def rod_cutting(prices, n):
    # prices[i] = price of rod of length i+1
    dp = [0] * (n + 1)
    
    for length in range(1, n + 1):   # each cut length
        price = prices[length - 1]
        for c in range(length, n + 1):
            dp[c] = max(dp[c], dp[c - length] + price)
    
    return dp[n]

prices = [1, 5, 8, 9, 10, 17, 17, 20]
print(rod_cutting(prices, 8))  # 22

Coin Change I: Færrest mønter

Coin Change I (et andet problem) spørger efter det mindste antal mønter, der skal bruges til at danne et målbeløb. Her er dp[c] = det mindste antal mønter, der skal bruges til at danne beløbet c. Rekurrens: dp[c] = min(dp[c], dp[c - coin] + 1). Initialisér alle poster til inf undtagen dp[0] = 0. Dette er også en ubegrænset variant (mønter kan genbruges), så gennemløb fra venstre mod højre. Returnér dp[amount], hvis værdien er endelig, ellers -1.

def coinChange(coins, amount):
    dp = [float('inf')] * (amount + 1)
    dp[0] = 0
    
    for coin in coins:
        for c in range(coin, amount + 1):
            dp[c] = min(dp[c], dp[c - coin] + 1)
    
    return dp[amount] if dp[amount] != float('inf') else -1

print(coinChange([1,5,6,9], 11))  # 2 (5+6 or other combos)
print(coinChange([2], 3))          # -1

Vigtig forskel: Maksimum kontra minimum kontra optælling

De tre varianter af den ubegrænsede rygsæk bruger forskellige operationer på dp[c-coin]: Maksimér værdien: dp[c] = max(dp[c], dp[c-w] + v); initialisér til 0. Minimér omkostningen: dp[c] = min(dp[c], dp[c-coin] + 1); initialisér til inf, dp[0]=0. Tæl måder: dp[c] += dp[c-coin]; initialisér til 0, dp[0]=1. At genkende, hvilken variant der gælder, er halvdelen af arbejdet i interviewopgaver.

Kompleksitet og interviewtip

Alle varianter af den ubegrænsede rygsæk kører på O(n × W) tid og kræver O(W) plads, hvor n er antallet af elementtyper, og W er målbeløbet. I møntopgaver er n antallet af møntværdier. I interviews skal du angive varianten (maksimum/minimum/optælling), skrive 1D-DP'en og være tydelig om, hvorvidt den ydre løkke går over mønter eller beløb — interviewere ved, at denne forskel afprøver en dyb forståelse af DP.

Sådan identificerer du ubegrænset kontra 0/1

Brug disse tegn til at identificere, hvilken variant der gælder: ubegrænset genbrug → ubegrænset (fremadrettet gennemløb); hvert element præcis én gang → 0/1 (baglæns gennemløb); problemet siger »et vilkårligt antal gange«, »ubegrænset forsyning« eller »genbrug tilladt« → ubegrænset. Eksempler: møntskifte, stangskæring og heltalsopdeling — alle er ubegrænsede. Delmængdesum, partitionering og 0/1-rygsæk — 0/1. Hvis du tager fejl her, får du forkerte svar, som er svære at fejlfinde.

Heltalsopdeling og andre varianter

Integer Break (LeetCode 343): Opdel et heltal n i mindst 2 positive heltal for at maksimere deres produkt. Dette er en ubegrænset rygsæk, hvor »elementerne« er heltallene fra 2 til n-1. Definér dp[i] = det maksimale produkt af heltal, hvis sum er i. For hvert element j fra 2 til i gælder dp[i] = max(dp[i], max(j, dp[j]) * max(i-j, dp[i-j])). Det viser, hvordan mønsteret for den ubegrænsede rygsæk kan generaliseres ud over møntproblemer.

def integerBreak(n):
    dp = [0] * (n + 1)
    dp[1] = 1
    for i in range(2, n + 1):
        for j in range(1, i):
            dp[i] = max(dp[i], max(j, dp[j]) * max(i-j, dp[i-j]))
    return dp[n]

print(integerBreak(10))  # 36 (3+3+4 = 3*3*4 = 36)

Hurtig kontrol

Kontrollér din forståelse af begreberne i Data Structures & Algorithms — Coding Interview Prep fra denne lektion.

Opsummering af lektionen

I denne lektion lærte du: den ubegrænsede rygsæk gennemløber kapaciteten fra venstre mod højre for at tillade genbrug af elementer, Coin Change II tæller kombinationer ved at placere mønten i den ydre løkke, og de tre varianter — maksimér, minimér og tæl — adskiller sig kun ved DP-operationen og initialiseringen. Næste emne er at bruge 0/1-rygsækken til at løse Partition Equal Subset Sum.

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 “Ubegrænset knapsack og Coin Change II” gratis?

Ja — hele teksten til “Ubegrænset knapsack og Coin Change II” 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 “Ubegrænset knapsack og Coin Change II”?

Tillad genbrug af elementer ved at gennemløbe kapaciteten forlæns, og løs coin-change-II (optælling af måder) samt rod-cutting med denne variant. 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 2 af 4.

Hvor lang tid tager lektionen “Ubegrænset knapsack og Coin Change II”?

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. 0/1-knapsack og pladseffektivisering
  2. Ubegrænset knapsack og Coin Change II
  3. Lige sum af delmængder
  4. Målsum med positive og negative fortegn
← Tilbage til Forberedelse til kodeinterviews