0Pricing
DSA Interview Prep · Pelajaran

Coin Change dan Tangga Berbiaya Minimum

Rumuskan relasi rekurensi coin-change dan min-cost-climbing-stairs, pilih arah DP yang tepat, lalu telusuri tabel secara manual.

Coin Change dan Tangga Berbiaya Minimum adalah pelajaran DSA Interview Prep gratis di CoddyKit. Ini adalah pelajaran 4 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar DSA Interview Prep, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus DSA Interview Prep mencakup 4 pelajaran total.

Pertukaran Koin: Masalahnya

Pertukaran Koin (LeetCode #322) memberi Anda beberapa pecahan koin dan jumlah sasaran. Temukan jumlah minimum koin yang diperlukan untuk membentuk jumlah tersebut secara tepat. Anda memiliki koin tak terbatas untuk setiap pecahan. Ini adalah varian ransel tak terbatas klasik — setiap item (koin) dapat digunakan berapa kali pun. Ini adalah salah satu masalah DP terpenting karena menguji kemampuan Anda dalam merumuskan rekurensi dari awal.

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

Pertukaran Koin: Penurunan Rekurensi

Definisikan dp[i] = jumlah minimum koin untuk membentuk jumlah i. Untuk setiap jumlah i, coba gunakan setiap koin c: jika i >= c, maka dp[i] = min(dp[i], 1 + dp[i-c]). Angka '1' mewakili koin yang baru saja digunakan; dp[i-c] adalah solusi optimal untuk jumlah yang tersisa. Ini mengasumsikan koin tersedia tanpa batas. Kasus dasar: dp[0] = 0. Inisialisasikan semua entri lainnya dengan nilai tak terhingga untuk menunjukkan bahwa entri tersebut 'belum dapat dicapai'.

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

Pertukaran Koin: Mengapa Pendekatan Rakus Gagal

Pendekatan rakus (selalu memilih koin terbesar yang sesuai) gagal pada masalah pertukaran koin. Contoh: koin=[1, 3, 4], jumlah=6. Pendekatan rakus memilih 4 lalu 1+1 = 3 koin. Solusi optimalnya adalah 3+3 = 2 koin. Pendekatan rakus berhasil untuk pecahan standar (1, 5, 10, 25 sen) karena pecahan tersebut kebetulan memenuhi sifat rakus. Namun, untuk himpunan koin sembarang, DP diperlukan. Ini adalah poin klasik dalam wawancara — menyatakan bahwa pendekatan rakus gagal dan menjelaskan alasannya menunjukkan kemampuan berpikir analitis yang kuat.

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

Pertukaran Koin II: Menghitung Cara

Pertukaran Koin II (LeetCode #518) meminta jumlah cara untuk membentuk suatu jumlah (bukan jumlah minimum koin). Rekurensinya berubah: alih-alih mencari nilai minimum, gunakan penjumlahan. dp[i] += dp[i-coin] untuk setiap koin. Urutan pengisian penting: untuk menghitung setiap kombinasi satu kali, iterasikan koin pada perulangan luar dan jumlah pada perulangan dalam. Jika perulangannya dibalik, yang dihitung adalah permutasi, bukan kombinasi (masalah yang berbeda).

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)

Tangga Biaya Minimum: Masalahnya

Menaiki Tangga dengan Biaya Minimum (LeetCode #746) memberi Anda sebuah tangga yang setiap anak tangganya memiliki biaya. Anda dapat menaiki 1 atau 2 anak tangga sekaligus. Temukan biaya minimum untuk mencapai puncak (satu langkah setelah anak tangga terakhir). Anda dapat mulai dari anak tangga 0 atau anak tangga 1 secara gratis. Masalah ini memadukan rekurensi menaiki tangga dengan pola minimisasi biaya pertukaran koin secara elegan, sehingga menjadi penghubung alami antara keduanya.

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

Tangga Biaya Minimum: Rekurensi

Definisikan dp[i] = biaya minimum untuk mencapai anak tangga i. Anda tiba di anak tangga i dengan membayar cost[i-1] (dari anak tangga i-1) atau cost[i-2] (dari anak tangga i-2). Jadi dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2]). Kasus dasar: dp[0] = 0 (mulai sebelum tangga, gratis), dp[1] = 0 (juga dapat mulai dari anak tangga 1, gratis). Jawabannya adalah dp[n], dengan 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

Optimasi Ruang Tangga Biaya Minimum

Karena dp[i] hanya bergantung pada dp[i-1] dan dp[i-2], kita dapat mengurangi ruang menjadi O(1) dengan dua variabel, seperti pada Fibonacci. Ganti larik dengan prev2 dan prev1. Perbarui keduanya pada setiap langkah. Ini adalah optimasi satu baris standar yang diharapkan pewawancara setelah Anda menyajikan solusi tabel O(n). Selalu sampaikan hal ini secara proaktif: 'Kita dapat menguranginya menjadi ruang O(1) karena kita hanya memerlukan dua nilai terakhir.'

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

Formulasi DP Alternatif

Beberapa masalah memiliki beberapa formulasi DP yang valid. Untuk tangga biaya minimum, Anda dapat mendefinisikan dp[i] = biaya minimum untuk LEAVE anak tangga i (membayar cost[i] lalu memilih untuk berpindah ke i+1 atau i+2). Kemudian dp[i] = cost[i] + min(dp[i+1], dp[i+2]) dengan pengisian dari kanan ke kiri, dan jawabannya adalah min(dp[0], dp[1]). Kedua formulasi tersebut benar. Berlatihlah menjelaskan formulasi yang Anda pilih dan alasannya — ini menunjukkan kelancaran Anda dalam menggunakan 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

Menghubungkan Pertukaran Koin dan Tangga

Pertukaran koin dan tangga biaya minimum sama-sama merupakan penerapan pola DP yang sama: pada setiap langkah, pilih satu opsi dari himpunan pilihan yang terbatas, lalu optimalkan suatu tujuan di sepanjang rangkaian pilihan tersebut. Perbedaannya hanya pada detail: pertukaran koin melacak jumlah (menambahkan 1 untuk setiap koin), sedangkan tangga melacak biaya (menambahkan cost[i] untuk setiap langkah). Mengenali struktur yang sama ini memungkinkan Anda menyelesaikan masalah DP baru dengan memetakannya ke pola yang sudah dikenal.

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

Jumlah Minimum Kuadrat Sempurna

Kuadrat Sempurna (LeetCode #279) meminta jumlah minimum kuadrat sempurna (1, 4, 9, 16, ...) yang jumlahnya sama dengan n. Ini persis seperti pertukaran koin, dengan 'koin' berupa bilangan kuadrat sempurna. Hasilkan semua kuadrat sempurna hingga n, lalu jalankan pertukaran koin. DP memerlukan O(n * sqrt(n)) time. Teorema Empat Kuadrat Lagrange menyatakan bahwa jawabannya paling banyak 4, yang juga memungkinkan pendekatan matematis O(sqrt(n)); tetapi DP adalah solusi yang diharapkan.

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

Menemukan Kesalahan Umum pada DP

Kesalahan umum pada DP: kasus dasar yang salah (dp[0] ditetapkan secara keliru), urutan pengisian yang salah (mengakses nilai yang belum dihitung), kesalahan satu posisi dalam definisi keadaan (dp[i] adalah biaya TO mencapai i dibandingkan biaya untuk LEAVE i), dan tidak mengembalikan -1 saat nilai tak terhingga masih tersisa (kasus yang mustahil). Selalu uji kasus paling sederhana (masukan kosong, satu elemen, sasaran=0) sebelum menguji masukan yang lebih besar.

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

Uji Cepat

Uji pemahaman Anda tentang konsep Struktur Data & Algoritma — Persiapan Wawancara Pemrograman dari pelajaran ini.

Rangkuman Pelajaran

Dalam pelajaran ini Anda mempelajari: DP jumlah minimum pada pertukaran koin (ransel tak terbatas) dan alasan pendekatan rakus gagal, pertukaran koin II untuk menghitung kombinasi dengan urutan koin di luar dan jumlah di dalam, serta tangga biaya minimum dengan formulasi dari kiri ke kanan dan dari kanan ke kiri. Berikutnya kita menjelajahi pola DP 1D dengan perampok rumah, algoritma Kadane, dan pemecahan kata.

Pertanyaan yang Sering Diajukan

Apakah pelajaran “Coin Change dan Tangga Berbiaya Minimum” gratis?

Ya — teks lengkap “Coin Change dan Tangga Berbiaya Minimum” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus DSA Interview Prep, upgrade ke CoddyKit PRO. Kursus DSA Interview Prep mencakup 4 pelajaran total.

Apa yang akan aku pelajari di “Coin Change dan Tangga Berbiaya Minimum”?

Rumuskan relasi rekurensi coin-change dan min-cost-climbing-stairs, pilih arah DP yang tepat, lalu telusuri tabel secara manual. Kamu berlatih DSA Interview Prep dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.

Apakah aku perlu pengalaman untuk memulai DSA Interview Prep?

Tidak diperlukan pengalaman sebelumnya. DSA Interview Prep di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 4 dari 4.

Berapa lama pelajaran “Coin Change dan Tangga Berbiaya Minimum” memakan waktu?

Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.

Bisakah aku menulis dan menjalankan kode dalam pelajaran DSA Interview Prep ini?

Ya. Setiap pelajaran DSA Interview Prep menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.

Semua pelajaran dalam kursus ini

  1. Mengenali DP: Submasalah yang Saling Tumpang Tindih
  2. DP Top-Down dengan Memoization
  3. DP Bottom-Up dengan Tabulasi
  4. Coin Change dan Tangga Berbiaya Minimum
← Kembali ke DSA Interview Prep