Förberedelse inför kodningsintervjuer · Lektion

Unbounded Knapsack och Coin Change II

Tillåt att objekt återanvänds genom att iterera kapaciteten framåt, och lös coin-change-II (räkna antal sätt) samt rod-cutting med denna variant.

Lektion 2 av 413 steg

Unbounded Knapsack och Coin Change II är en gratis lektion i Förberedelse inför kodningsintervjuer på CoddyKit. Detta är lektion 2 av 4. Ni kan läsa hela lektionen gratis nedan och sedan öva praktiskt i webbläsaren med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt. Den ingår i lärvägen för Förberedelse inför kodningsintervjuer, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Förberedelse inför kodningsintervjuer innehåller totalt 4 lektioner.

Det obegränsade ryggsäcksproblemet

I det obegränsade ryggsäcksproblemet kan varje objekt väljas ett godtyckligt antal gånger (till skillnad från 0/1-ryggsäcksproblemet, där varje objekt används högst en gång). Tillståndsdefinitionen är densamma — dp[c] = det maximala värdet som kan uppnås med kapaciteten c — men iterationsriktningen ändras. Eftersom objekten kan återanvändas vill vi tillåta att det aktuella objektet används igen när vi uppdaterar dp[c], så vi itererar över kapaciteten från vänster till höger (framåt).

Framåtriktad iteration möjliggör återanvändning

Kom ihåg att vi i 0/1-ryggsäcksproblemet itererade från höger till vänster för att förhindra återanvändning. I det obegränsade ryggsäcksproblemet gör vi tvärtom: vi itererar från vänster till höger. När vi beräknar dp[c] har dp[c-w] redan uppdaterats i den aktuella genomgången — vilket betyder att objekt i redan kan ha inkluderats. Det är precis vad vi vill: objekt i kan läggas till igen i en lösning som redan innehåller objekt 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: räkna antalet sätt

Coin Change II frågar: givet myntvalörer och ett belopp, hur många olika sätt finns det att skapa beloppet (varje mynt kan användas obegränsat många gånger)? Detta är en variant av obegränsad ryggsäck där vi i stället för att maximera ett värde räknar kombinationer. Definiera dp[c] som antalet sätt att skapa beloppet c. Grundfall: dp[0] = 1 (ett sätt att skapa 0: att inte ta något).

Implementering av Coin Change II

För varje mynt itererar ni över beloppen från vänster till höger och summerar: dp[c] += dp[c - coin]. Grundfallet dp[0] = 1 initierar räkningen. Observera att den yttre loopen går över mynten och den inre loopen över beloppen — detta ger naturligt kombinationsantal (inte permutationer), eftersom varje myntvalör behandlas exakt en gång i en yttre genomgång.

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 jämfört med permutationer

Looparnas ordning är avgörande. Om vi placerar beloppet i den yttre loopen och myntet i den inre loopen räknar vi permutationer (ordningen spelar roll). För amount=5 med mynten [1,2] räknas 1+2+2 och 2+1+2 separat. Om vi placerar myntet i den yttre loopen räknar vi kombinationer (ordningen spelar ingen roll): 1+2+2 och 2+1+2 är samma. Coin Change II frågar efter kombinationer, så myntet ska ligga i den yttre loopen.

# 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

Stavkapningsproblemet

Ett annat klassiskt problem med obegränsad ryggsäck: givet en stav med längden n och priser för varje stavlängd från 1 till n, hitta den maximala intäkten genom att kapa staven optimalt. Varje del med längden l kan säljas för price[l], och delarna kan återanvändas (staven kan kapas i flera delar med samma längd). Detta motsvarar direkt obegränsad ryggsäck med W = n, där objekten är de olika kaplängderna.

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: minimalt antal mynt

Coin Change I (ett annat problem) frågar efter det minsta antalet mynt som krävs för att skapa ett målbelopp. Här är dp[c] = det minsta antalet mynt för att skapa beloppet c. Rekurrens: dp[c] = min(dp[c], dp[c - coin] + 1). Initiera alla poster till inf utom dp[0] = 0. Detta är också en obegränsad variant (mynt kan återanvändas), så iterera från vänster till höger. Returnera dp[amount] om värdet är ändligt, annars -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

Viktig skillnad: max, min och antal

De tre varianterna av obegränsad ryggsäck använder olika operationer på dp[c-coin]: Maximera värdet: dp[c] = max(dp[c], dp[c-w] + v); initiera med 0. Minimera kostnaden: dp[c] = min(dp[c], dp[c-coin] + 1); initiera med inf, dp[0]=0. Räkna antalet sätt: dp[c] += dp[c-coin]; initiera med 0, dp[0]=1. Att känna igen vilken variant som gäller är halva utmaningen i intervjuproblem.

Komplexitet och intervjutips

Alla varianter av obegränsad ryggsäck körs på O(n × W) tid och kräver O(W) utrymme, där n är antalet objekttyper och W är målbeloppet. För myntproblem är n antalet myntvalörer. Under intervjuer bör ni ange varianten (maximera/minimera/räkna), skriva 1D-DP och vara tydliga med om den yttre loopen går över mynt eller belopp — intervjuare vet att denna skillnad testar en djup förståelse av DP.

Identifiera obegränsad ryggsäck jämfört med 0/1

Använd följande signaler för att avgöra vilken variant som gäller: obegränsad återanvändning → obegränsad variant (framåtriktad iteration); varje objekt exakt en gång → 0/1 (bakåtriktad iteration); om problemet säger ”valfritt antal gånger”, ”obegränsad tillgång” eller ”återanvändning tillåten” → obegränsad variant. Exempel: Coin Change, Rod Cutting och Integer Break — alla är obegränsade. Subset Sum, Partition och 0/1 Knapsack — 0/1. Om ni väljer fel blir svaret fel, och sådana fel kan vara svåra att felsöka.

Integer Break och andra varianter

Integer Break (LeetCode 343): dela upp ett heltal n i minst två positiva heltal för att maximera deras produkt. Detta är en variant av obegränsad ryggsäck där ”objekten” är heltalen 2 till n-1. Definiera dp[i] = den maximala produkten av heltal vars summa är i. För varje objekt j från 2 till i gäller dp[i] = max(dp[i], max(j, dp[j]) * max(i-j, dp[i-j])). Detta visar hur mönstret för obegränsad ryggsäck kan generaliseras bortom myntproblem.

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)

Snabbkontroll

Testa er förståelse av koncepten från Data Structures & Algorithms — Coding Interview Prep som tas upp i den här lektionen.

Tillbakablick på lektionen

I den här lektionen har ni lärt er: obegränsad ryggsäck itererar över kapaciteten från vänster till höger för att tillåta återanvändning av objekt, Coin Change II räknar kombinationer genom att placera coin i den yttre loopen, och de tre varianterna — maximera, minimera och räkna — skiljer sig endast i DP-operationen och initieringen. Härnäst använder vi 0/1-ryggsäcksproblemet för att lösa Partition Equal Subset Sum.

Gratis att börja

Lär dig Förberedelse inför kodningsintervjuer 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
90
Lektioner
360

Vanliga frågor

Är lektionen ”Unbounded Knapsack och Coin Change II” gratis?

Ja – hela texten till ”Unbounded Knapsack och Coin Change II” kan läsas gratis här på webben. Om Ni vill öva interaktivt med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt och låsa upp resten av kursen i Förberedelse inför kodningsintervjuer, kan Ni uppgradera till CoddyKit PRO. Kursen i Förberedelse inför kodningsintervjuer innehåller totalt 4 lektioner.

Vad lär jag mig i ”Unbounded Knapsack och Coin Change II”?

Tillåt att objekt återanvänds genom att iterera kapaciteten framåt, och lös coin-change-II (räkna antal sätt) samt rod-cutting med denna variant. Ni övar på Förberedelse inför kodningsintervjuer 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 Förberedelse inför kodningsintervjuer?

Du behöver inga förkunskaper. Utbildningen i Förberedelse inför kodningsintervjuer 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 2 av 4.

Hur lång tid tar lektionen ”Unbounded Knapsack och Coin Change II”?

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 Förberedelse inför kodningsintervjuer-lektionen?

Ja. Varje Förberedelse inför kodningsintervjuer-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

  1. 0/1-knapsack och utrymmesoptimering
  2. Unbounded Knapsack och Coin Change II
  3. Partition Equal Subset Sum
  4. Target Sum med positiva och negativa tecken
← Tillbaka till Förberedelse inför kodningsintervjuer