Forberedelse til kodeintervjuer · leksjon

Coin Change og trapp med minimale kostnader

Formuler rekurrensene for coin-change og min-cost-climbing-stairs, velg riktig DP-retning og følg tabellen manuelt.

Leksjon 4 av 413 trinn

Coin Change og trapp med minimale kostnader 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.

Myntveksling: Problemet

Myntveksling (LeetCode #322) gir Dem myntvalører og et målbeløp. Finn minimumsantallet mynter som trengs for å oppnå nøyaktig dette beløpet. De har ubegrenset tilgang på mynter av hver valør. Dette er en klassisk variant av ubegrenset ryggsekk — hvert element (hver mynt) kan brukes et hvilket som helst antall ganger. Det er et av de viktigste DP-problemene fordi det tester evnen Deres til å formulere en rekurrens fra grunnen av.

# Problem examples:
# coins=[1,5,6,9], amount=11 -> 2 (5+6 or 2+9? no: 5+6=11 YES)
# coins=[2],       amount=3  -> -1 (impossible)
# coins=[1,2,5],   amount=11 -> 3 (5+5+1)
# coins=[186,419,83,408], amount=6249 -> 20

# Key choices:
# - Try each coin denomination at each step
# - Minimum coins = 1 + minimum(coins to make amount - coin)
# - If amount < 0: impossible
# - If amount = 0: done (0 coins)

print('Coin change: unbounded knapsack, find minimum count')

Myntveksling: Utledning av rekurrensen

Definer dp[i] = minimumsantallet mynter for å oppnå beløpet i. For hvert beløp i prøver De å bruke hver mynt c: hvis i >= c, er dp[i] = min(dp[i], 1 + dp[i-c]). Tallet «1» står for mynten vi nettopp brukte; dp[i-c] er den optimale løsningen for det gjenværende beløpet. Dette forutsetter et ubegrenset antall mynter. Basistilfellet er dp[0] = 0. Initialiser alle andre oppføringer med uendelig for å representere «ikke oppnåelig ennå».

def coin_change(coins, amount):
    # dp[i] = min coins to make amount i
    dp = [float('inf')] * (amount + 1)
    dp[0] = 0  # base: 0 coins for amount 0

    for i in range(1, amount + 1):
        for coin in coins:
            if i >= coin and dp[i - coin] != float('inf'):
                dp[i] = min(dp[i], 1 + dp[i - coin])

    return dp[amount] if dp[amount] != float('inf') else -1

print(coin_change([1, 5, 6, 9], 11))  # 2
print(coin_change([2], 3))             # -1
print(coin_change([1, 2, 5], 11))      # 3

# Trace dp for coins=[1,5] amount=6:
# dp[0]=0, dp[1]=1, dp[2]=2, dp[3]=3, dp[4]=4, dp[5]=1, dp[6]=2

Myntveksling: Hvorfor grådig algoritme ikke fungerer

En grådig algoritme (velg alltid den største mynten som passer) fungerer ikke for myntveksling. Eksempel: coins=[1, 3, 4], amount=6. Den grådige algoritmen velger 4 og deretter 1+1, altså 3 mynter. Det optimale valget er 3+3, altså 2 mynter. En grådig algoritme fungerer for standardvalører (1, 5, 10 og 25 cent) fordi disse tilfeldigvis oppfyller den grådige egenskapen. For vilkårlige myntsett kreves imidlertid DP. Dette er et klassisk poeng i jobbintervjuer — å si at en grådig algoritme ikke fungerer, og forklare hvorfor, viser sterke analytiske ferdigheter.

# Greedy failure example:
# coins=[1,3,4], amount=6
# Greedy: 4 (rem=2), 1 (rem=1), 1 (rem=0) -> 3 coins
# Optimal: 3 (rem=3), 3 (rem=0) -> 2 coins

def coin_change_greedy_wrong(coins, amount):
    coins_sorted = sorted(coins, reverse=True)
    count = 0
    for coin in coins_sorted:
        while amount >= coin:
            amount -= coin
            count += 1
    return count if amount == 0 else -1

print('Greedy:', coin_change_greedy_wrong([1,3,4], 6))  # 3 (WRONG)
print('DP:    ', coin_change([1,3,4], 6))               # 2 (CORRECT)

Myntveksling II: Tell antallet måter

Myntveksling II (LeetCode #518) spør etter antallet måter å oppnå beløpet på (ikke minimumsantallet). Rekurrensen endres: I stedet for min bruker vi sum. dp[i] += dp[i-coin] for hver mynt. Utfyllingsrekkefølgen er viktig: For å telle hver kombinasjon én gang itererer De over mynter i den ytre løkken og beløp i den indre løkken. Hvis løkkene byttes om, telles permutasjoner i stedet for kombinasjoner (et annet problem).

def coin_change_ii(coins, amount):
    # dp[i] = number of ways to make amount i
    dp = [0] * (amount + 1)
    dp[0] = 1  # one way to make amount 0: use no coins

    # Outer loop: coins -- ensures each coin type processed once
    for coin in coins:
        # Inner loop: amounts
        for i in range(coin, amount + 1):
            dp[i] += dp[i - coin]

    return dp[amount]

print(coin_change_ii([1, 2, 5], 5))   # 4: [1,1,1,1,1],[1,1,1,2],[1,2,2],[5]
print(coin_change_ii([2], 3))          # 0: impossible
print(coin_change_ii([10], 10))        # 1

# Key: coin outer, amount inner = COMBINATIONS (unordered)
# Reverse (amount outer, coin inner) = PERMUTATIONS (ordered)

Trappetrinn med minimumskostnad: Problemet

Trappetrinn med minimumskostnad (LeetCode #746) gir Dem en trapp der hvert trinn har en kostnad. De kan gå 1 eller 2 trinn om gangen. Finn minimumskostnaden for å nå toppen (ett trinn etter det siste trappetrinnet). De kan starte gratis på trinn 0 eller trinn 1. Dette problemet kombinerer på en elegant måte rekurrensen for trappegåing med mønsteret for kostnadsminimering i myntveksling, og fungerer derfor som en naturlig overgang mellom de to.

# cost = [10, 15, 20]
# Pay cost[i] to leave step i
# You can step to i+1 or i+2
# Goal: reach top (index 3) with minimum cost

# Path options:
# Start at 0: cost 10, go to 2: cost 20, done -> 30
# Start at 1: cost 15, go to 3: done -> 15  <- OPTIMAL
# Start at 0: cost 10, go to 1: cost 15 -> 25

cost = [10, 15, 20]
# Optimal: start at step 1, pay 15, jump to top -> cost = 15
print('Expected:', 15)

Trappetrinn med minimumskostnad: Rekurrensen

Definer dp[i] = minimumskostnaden for å nå trinn i. De kommer til trinn i ved å betale cost[i-1] (fra trinn i-1) eller cost[i-2] (fra trinn i-2). Derfor er dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2]). Basistilfeller: dp[0] = 0 (starten før trappen, gratis), dp[1] = 0 (De kan også starte på trinn 1, gratis). Svaret er dp[n], der n = len(cost).

def min_cost_climbing_stairs(cost):
    n = len(cost)
    # dp[i] = minimum cost to reach step i
    # Steps 0 to n; step n is the top (goal)
    dp = [0] * (n + 1)
    # dp[0] = 0 (free to start here)
    # dp[1] = 0 (free to start here)
    for i in range(2, n + 1):
        dp[i] = min(dp[i-1] + cost[i-1],   # step from i-1
                    dp[i-2] + cost[i-2])    # jump from i-2
    return dp[n]

print(min_cost_climbing_stairs([10, 15, 20]))      # 15
print(min_cost_climbing_stairs([1,100,1,1,1,100,1,1,100,1]))  # 6

Trappetrinn med minimumskostnad: Minneoptimalisering

Siden dp[i] bare avhenger av dp[i-1] og dp[i-2], kan vi redusere minnebruken til O(1) med to variabler, akkurat som i Fibonacci. Erstatt arrayen med prev2 og prev1. Oppdater dem ved hvert trinn. Dette er en standardoptimalisering på én linje som intervjuere forventer etter at De har presentert tabelløsningen med O(n). Nevn det alltid på eget initiativ: «Vi kan redusere dette til O(1) minne siden vi bare trenger de to siste verdiene.»

def min_cost_optimised(cost):
    n = len(cost)
    prev2, prev1 = 0, 0  # dp[0] and dp[1]
    for i in range(2, n + 1):
        curr = min(prev1 + cost[i-1], prev2 + cost[i-2])
        prev2, prev1 = prev1, curr
    return prev1

print(min_cost_optimised([10, 15, 20]))  # 15
print(min_cost_optimised([1,100,1,1,1,100,1,1,100,1]))  # 6

# Alternative: directly use cost array as rolling storage
def min_cost_v2(cost):
    n = len(cost)
    for i in range(2, n):
        cost[i] += min(cost[i-1], cost[i-2])
    return min(cost[-1], cost[-2])

from copy import deepcopy
cost_test = [10,15,20]
print(min_cost_v2(deepcopy(cost_test)))  # 15

Alternativ DP-formulering

Noen problemer har flere gyldige DP-formuleringer. For trappetrinn med minimumskostnad kan De definere dp[i] = minimumskostnaden for å FORLATE trinn i (betal cost[i] og velg å gå til i+1 eller i+2). Da er dp[i] = cost[i] + min(dp[i+1], dp[i+2]) når tabellen fylles fra høyre mot venstre, og svaret er min(dp[0], dp[1]). Begge formuleringene er riktige. Øv på å forklare hvilken formulering De valgte, og hvorfor — det viser at De behersker DP.

def min_cost_alternative(cost):
    n = len(cost)
    # dp[i] = min cost when starting FROM step i
    # Fill right to left
    dp = cost[:] + [0]  # dp[n] = 0 (already at top)
    for i in range(n - 1, -1, -1):
        # Pay cost[i], then choose i+1 or i+2
        if i + 2 <= n:
            dp[i] = cost[i] + min(dp[i+1], dp[i+2])
        else:
            dp[i] = cost[i] + dp[i+1]
    # Can start at step 0 or step 1
    return min(dp[0], dp[1])

print(min_cost_alternative([10, 15, 20]))  # 15
print(min_cost_alternative([1,100,1,1,1,100,1,1,100,1]))  # 6

Sammenhengen mellom myntveksling og trappetrinn

Både myntveksling og trappetrinn med minimumskostnad er eksempler på det samme DP-mønsteret: Ved hvert trinn velger man fra et begrenset sett med alternativer og optimaliserer et mål over sekvensen av valg. Forskjellene er overfladiske: Myntveksling holder styr på antallet (legger til 1 per mynt), mens trappetrinn holder styr på kostnaden (legger til cost[i] per trinn). Når De kjenner igjen denne felles strukturen, kan De løse nye DP-problemer ved å knytte dem til kjente maler.

# Shared pattern:
# dp[state] = optimise(dp[prev_state_1] + cost_1,
#                      dp[prev_state_2] + cost_2, ...)

# Coin change:  dp[amount] = min(1 + dp[amount - coin] for coin in coins)
# Min stair:    dp[step]   = min(cost[step-1]+dp[step-1], cost[step-2]+dp[step-2])
# Max path sum: dp[cell]   = max(dp[top], dp[left]) + grid[cell]
# House robber: dp[house]  = max(dp[house-1], dp[house-2] + value[house])

# All four are the SAME pattern with different:
# - State representation
# - Number of choices per state
# - Objective (min/max)
# - Transition cost
print('DP pattern: state + choices + objective + cost = template')

Minimumsantall perfekte kvadrater

Perfekte kvadrater (LeetCode #279) spør etter minimumsantallet perfekte kvadrater (1, 4, 9, 16, ...) som summerer til n. Dette er nøyaktig myntveksling der «myntene» er perfekte kvadrater. Generer alle perfekte kvadrater opptil n, og kjør deretter myntveksling. DP gir O(n * sqrt(n))-tid. Lagranges firekvadratteorem sier at svaret høyst er 4, noe som også muliggjør en matematisk tilnærming med O(sqrt(n)) — men DP er den forventede løsningen.

import math

def num_squares(n):
    # Generate all perfect squares up to n
    squares = [i*i for i in range(1, int(math.sqrt(n)) + 1)]
    # Coin change with squares as 'coins'
    dp = [float('inf')] * (n + 1)
    dp[0] = 0
    for i in range(1, n + 1):
        for sq in squares:
            if i >= sq:
                dp[i] = min(dp[i], 1 + dp[i - sq])
    return dp[n]

print(num_squares(12))  # 3: 4+4+4
print(num_squares(13))  # 2: 4+9
print(num_squares(1))   # 1: 1

Feilsøking av DP: Vanlige feil

Vanlige DP-feil: feil basistilfelle (dp[0] er satt feil), feil utfyllingsrekkefølge (man leser en verdi som ikke er beregnet ennå), feil med én i indeks eller tilstandens definisjon (dp[i] er kostnaden FOR å nå i, eller kostnaden for å FORLATE i), og at man ikke returnerer -1 når uendelig fortsatt gjenstår (umulige tilfeller). Test alltid de enkleste tilfellene først (tom inndata, ett element, target=0) før De tester større inndata.

# Common DP debugging checklist:
# 1. Base case: what is dp[0]? dp[1]? Are they correct?
# 2. State definition: write it in English before coding
# 3. Recurrence: trace manually on a 3-element example
# 4. Fill order: dependency arrows point left/up? Fill left/up first
# 5. Infinity check: return -1 or 0 when dp[target] == inf?
# 6. Array bounds: dp has size n+1 for 0..n, or n for 0..n-1?

# Quick test template:
def test_coin_change():
    assert coin_change([1], 0) == 0     # base case
    assert coin_change([1], 1) == 1     # single coin
    assert coin_change([2], 3) == -1    # impossible
    assert coin_change([1,5,6,9], 11) == 2
    print('All tests passed!')

test_coin_change()

Kunnskapssjekk

Test forståelsen Deres av begrepene fra Data Structures & Algorithms — Coding Interview Prep i denne leksjonen.

Oppsummering av leksjonen

I denne leksjonen har De lært: DP for minimumsantallet i myntveksling (ubegrenset ryggsekk) og hvorfor en grådig algoritme ikke fungerer, myntveksling II for å telle kombinasjoner med mynter i ytre løkke og beløp i indre løkke, samt trappetrinn med minimumskostnad med både venstre-til-høyre- og høyre-til-venstre-formuleringer. Deretter skal vi utforske 1D-DP-mønstre med husrøveren, Kadane's algorithm og orddeling.

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 «Coin Change og trapp med minimale kostnader» gratis?

Ja – hele teksten i «Coin Change og trapp med minimale kostnader» 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 «Coin Change og trapp med minimale kostnader»?

Formuler rekurrensene for coin-change og min-cost-climbing-stairs, velg riktig DP-retning og følg tabellen manuelt. 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 «Coin Change og trapp med minimale kostnader»?

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. Gjenkjenne DP: overlappende delproblemer
  2. Top-down-DP med memoisation
  3. Bottom-up-DP med tabulering
  4. Coin Change og trapp med minimale kostnader
← Tilbake til Forberedelse til kodeintervjuer