DP Top-Down dengan Memoization
Tambahkan dict memo ke solusi rekursif untuk memangkas pemanggilan duplikat, lalu gunakan @lru_cache untuk melakukan memoization dengan kode minimal.
DP Top-Down dengan Memoization adalah pelajaran Coding Interview Prep gratis di CoddyKit. Ini adalah pelajaran 2 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 Coding Interview Prep, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus Coding Interview Prep mencakup 4 pelajaran total.
DP dari Atas ke Bawah: Gagasan Memoisasi
DP dari atas ke bawah dimulai dari solusi rekursif asli dan menambahkan memoisasi: penyimpanan hasil setiap submasalah saat pertama kali dihitung. Pada pemanggilan berikutnya dengan argumen yang sama, hasil yang tersimpan langsung dikembalikan tanpa melakukan rekursi. Ini mengubah rekursi naif O(2^n) menjadi O(n) dengan perubahan kode yang minimal — sering kali cukup dengan menambahkan 2–3 baris ke solusi rekursif yang sudah ada.
# Top-down approach:
# 1. Write the recursive solution (natural but slow)
# 2. Add a memo dict to cache results
# 3. Before recursing, check if the result is cached
# 4. Before returning, store the result in the cache
# This is also called 'memoization' (US spelling)
# 'memoize' means 'to remember', not 'memorize'
# The cache key is the function arguments
# For fib: key is n
# For 2D DP: key is (i, j)
# For 3D DP: key is (i, j, k)
print('Top-down = recursion + memo cache')Fibonacci dengan Memoisasi
Menambahkan kamus memo ke rekursi Fibonacci naif mengurangi waktu dari O(2^n) menjadi O(n). Pemanggilan pertama untuk fib(k) menghitung dan menyimpan hasilnya. Semua pemanggilan berikutnya untuk k yang sama langsung mengembalikan nilai yang tersimpan. Kompleksitas ruangnya adalah O(n) untuk kamus memo ditambah O(n) untuk tumpukan pemanggilan. Bandingkan jumlah pemanggilannya: tanpa memoisasi, fib(30) melakukan sekitar 2 juta pemanggilan; dengan memoisasi, tepat 30 pemanggilan.
def fib_memo(n, memo=None):
if memo is None:
memo = {}
if n in memo:
return memo[n] # return cached result
if n <= 1:
return n
memo[n] = fib_memo(n-1, memo) + fib_memo(n-2, memo)
return memo[n]
# Verify speed improvement:
print(fib_memo(30)) # fast!
print(fib_memo(50)) # still fast
print(fib_memo(100)) # no problem
# Without memo, fib_naive(50) would take minutes
# With memo: each of the 50 sub-problems computed onceMenggunakan @functools.lru_cache
Dekorator @functools.lru_cache(maxsize=None) Python (atau alias @cache dalam Python 3.9+) secara otomatis melakukan memoisasi pada fungsi berdasarkan argumennya. Ini adalah cara paling rapi untuk menambahkan DP dari atas ke bawah dalam wawancara — tulis solusi rekursif, tambahkan dekorator, dan selesai. Dekorator tersebut menyimpan semua hasil dalam kamus yang kuncinya berasal dari argumen fungsi, yang harus dapat di-hash (bukan daftar — gunakan tupel sebagai gantinya).
import functools
@functools.lru_cache(maxsize=None)
def fib(n):
if n <= 1:
return n
return fib(n-1) + fib(n-2)
print(fib(50)) # 12586269025
print(fib(100)) # works instantly
# Clear cache between tests if needed:
fib.cache_clear()
# Python 3.9+ shorthand:
# from functools import cache
# @cache
# def fib(n): ...
print(fib.cache_info()) # shows hits, misses, maxsize, currsizePenukaran Koin dari Atas ke Bawah
Penukaran Koin (LeetCode #322): diberikan pecahan nilai koin dan jumlah sasaran, temukan jumlah minimum koin yang diperlukan. Formulasi rekursifnya: untuk setiap koin, ambil koin tersebut dan selesaikan masalah untuk jumlah yang tersisa, lalu ambil nilai minimum. Lakukan memoisasi berdasarkan jumlah tersebut untuk menghindari penghitungan ulang. Kasus dasar: amount=0 membutuhkan 0 koin; jumlah yang tidak mungkin dicapai mengembalikan tak terhingga (atau -1 setelah rekursi selesai).
import functools
def coin_change_top_down(coins, amount):
@functools.lru_cache(maxsize=None)
def dp(remaining):
if remaining == 0:
return 0 # no coins needed
if remaining < 0:
return float('inf') # impossible
# Try each coin and take the minimum
return 1 + min(dp(remaining - c) for c in coins)
result = dp(amount)
return result if result != float('inf') else -1
print(coin_change_top_down([1, 5, 6, 9], 11)) # 2: (5+6) or (2*5+1?no: 9+2?no) 5+6=11 YES
print(coin_change_top_down([2], 3)) # -1: impossible
print(coin_change_top_down([1, 2, 5], 11)) # 3: 5+5+1Menaiki Tangga dari Atas ke Bawah dengan K Langkah
Generalisasikan masalah menaiki tangga agar memungkinkan 1 hingga k langkah. Keadaannya adalah anak tangga saat ini, dan dari anak tangga i Anda dapat mencapai anak tangga i+1, i+2, ..., i+k. Rekurensinya: dp(i) = sum of dp(i-j) for j in 1..k if i-j >= 0. Memoisasi menjadikannya O(n*k), bukan O(k^n). Generalisasi ini muncul dalam masalah seperti 'biaya minimum untuk mencapai anak tangga terakhir' dan 'menghitung cara untuk mengisi kisi'.
import functools
def climb_k_steps(n, k):
@functools.lru_cache(maxsize=None)
def dp(i):
if i == 0:
return 1 # base: one way to stay at ground
if i < 0:
return 0 # impossible
# From stair i, you could have come from i-1, i-2, ..., i-k
return sum(dp(i - j) for j in range(1, k+1) if i - j >= 0)
return dp(n)
# k=2 (original): should match fib-like sequence
print([climb_k_steps(n, 2) for n in range(7)]) # [1,1,2,3,5,8,13]
# k=3: more options
print([climb_k_steps(n, 3) for n in range(7)]) # [1,1,2,4,7,13,24]LCS dari Atas ke Bawah: Memoisasi 2D
Subsekuens Bersama Terpanjang (LCS) memerlukan keadaan 2D: dp(i, j) = panjang LCS dari s1[:i] dan s2[:j]. Jika s1[i-1] == s2[j-1], karakternya cocok: dp(i,j) = 1 + dp(i-1, j-1). Jika tidak: dp(i,j) = max(dp(i-1,j), dp(i,j-1)) — lewati satu karakter dari salah satu untaian. Memoisasi berdasarkan (i, j) menghasilkan O(mn), bukan O(2^(m+n)).
import functools
def lcs_top_down(s1, s2):
m, n = len(s1), len(s2)
@functools.lru_cache(maxsize=None)
def dp(i, j):
if i == 0 or j == 0:
return 0 # empty prefix has LCS of 0
if s1[i-1] == s2[j-1]:
return 1 + dp(i-1, j-1) # characters match
return max(dp(i-1, j), dp(i, j-1)) # skip one
return dp(m, n)
print(lcs_top_down('abcde', 'ace')) # 3: 'ace'
print(lcs_top_down('abc', 'abc')) # 3: 'abc'
print(lcs_top_down('abc', 'def')) # 0: no common charsKamus Memo vs lru_cache: Kapan Memilihnya
Gunakan @lru_cache ketika argumen fungsi Anda berupa tipe primitif yang dapat di-hash (int, str, tuple). Gunakan kamus memo manual ketika: Anda perlu meneruskan keadaan yang dapat berubah (daftar, kamus) dengan mengubahnya menjadi tupel, perlu melacak kunci yang sudah dihitung, atau berada dalam metode kelas ketika self tidak seharusnya dimemoisasi. Kamus memo manual lebih eksplisit dan menghindari masalah penutupan yang sulit dideteksi dalam fungsi pembantu rekursif.
# @lru_cache: clean, automatic, O(1) overhead
# Use when: arguments are simple (int, str, tuple)
import functools
@functools.lru_cache(maxsize=None)
def simple_dp(n):
if n <= 1: return n
return simple_dp(n-1) + simple_dp(n-2)
# Manual memo dict: explicit, flexible
# Use when: complex state, need to inspect memo, class methods
def manual_memo_dp(s1, s2):
memo = {}
def dp(i, j):
if (i,j) in memo: return memo[(i,j)]
if i == 0 or j == 0:
return 0
if s1[i-1] == s2[j-1]:
memo[(i,j)] = 1 + dp(i-1, j-1)
else:
memo[(i,j)] = max(dp(i-1,j), dp(i,j-1))
return memo[(i,j)]
return dp(len(s1), len(s2))
print(manual_memo_dp('abcde', 'ace')) # 3Jumlah Target dari Atas ke Bawah
Jumlah Target (LeetCode #494): tetapkan tanda + atau - pada setiap angka dan hitung penetapan tanda yang menghasilkan jumlah target. Keadaan: dp(index, current_sum). Pada setiap indeks, cobalah menambahkan (+) dan mengurangkan (-) angka saat ini. Memoisasi berdasarkan (index, current_sum) mengubah pencarian menyeluruh O(2^n) menjadi O(n * sum_range). Rentang jumlah dibatasi oleh total semua angka, sehingga terdapat O(n * S) keadaan secara keseluruhan.
import functools
def find_target_sum_ways(nums, target):
@functools.lru_cache(maxsize=None)
def dp(index, current_sum):
if index == len(nums):
return 1 if current_sum == target else 0
# Try adding the number
add = dp(index + 1, current_sum + nums[index])
# Try subtracting the number
subtract = dp(index + 1, current_sum - nums[index])
return add + subtract
return dp(0, 0)
print(find_target_sum_ways([1,1,1,1,1], 3)) # 5
print(find_target_sum_ways([1], 1)) # 1
print(find_target_sum_ways([1], -1)) # 1Dari Atas ke Bawah vs Dari Bawah ke Atas: Kelebihan dan Kekurangan
Dari atas ke bawah (memoisasi) memiliki keunggulan: alami untuk ditulis (dimulai dari solusi rekursif), hanya menghitung submasalah yang benar-benar diperlukan (malas), dan mudah ditambahkan secara bertahap. Dari bawah ke atas (tabulasi) memiliki keunggulan: tidak ada overhead tumpukan pemanggilan (tidak ada batas rekursi Python), akses memori yang lebih efisien, dan lebih mudah dioptimalkan penggunaan ruangnya. Keduanya memiliki kompleksitas asimtotik yang sama. Dalam wawancara, mulailah dari atas ke bawah untuk memverifikasi kebenaran, lalu ubah ke bawah ke atas jika diminta menggunakan ruang yang lebih baik.
# Top-down advantages:
# + Natural: write recursive, add @cache
# + Lazy: only computes needed sub-problems
# + Easy to reason about correctness
# - Uses call stack (recursion limit in Python)
# - Higher constant factor (function call overhead)
# Bottom-up advantages:
# + No recursion limit
# + Better cache performance (sequential memory)
# + Easier to space-optimise (rolling array)
# - Must compute all sub-problems in order
# - Less intuitive for complex 2D/3D problems
# Interview strategy:
# Start with top-down to verify recurrence,
# convert to bottom-up only if asked.
print('Top-down: easy to write | Bottom-up: efficient for large n')Pemisahan Kata dengan DP dari Atas ke Bawah
Pemisahan Kata (LeetCode #139) menanyakan apakah untaian s dapat dipecah menjadi kata-kata dari sebuah kamus. Keadaan: dp(i) = apakah s[i:] dapat dipecah. Dari indeks i, cobalah semua kata: jika s[i:i+len(w)] == w, lakukan rekursi pada sufiks yang tersisa. Memoisasi berdasarkan indeks awal mengubah pencarian menyeluruh O(2^n) menjadi O(n^2) (atau O(n * max_word_len)) dengan pemeriksaan keanggotaan himpunan.
import functools
def word_break(s, word_dict):
word_set = set(word_dict)
@functools.lru_cache(maxsize=None)
def dp(start):
if start == len(s):
return True # successfully segmented entire string
for end in range(start + 1, len(s) + 1):
if s[start:end] in word_set and dp(end):
return True
return False
return dp(0)
print(word_break('leetcode', ['leet', 'code'])) # True
print(word_break('applepenapple', ['apple', 'pen'])) # True
print(word_break('catsandog', ['cats', 'dog', 'and', 'cat', 'san', 'andog'])) # FalseBatas Rekursi dan Itertools
Batas rekursi bawaan Python adalah 1000 (ditetapkan oleh sys.getrecursionlimit()). Untuk masalah DP pada masukan besar (n = 10,000+), memoization dari atas ke bawah akan mencapai batas ini. Pilihannya: tingkatkan batas dengan sys.setrecursionlimit(100000), atau ubah ke DP dari bawah ke atas. Dalam pemrograman kompetitif, meningkatkan batas ini merupakan hal yang umum; dalam kode produksi, selalu pilih solusi dari bawah ke atas atau solusi iteratif demi keandalan.
import sys
print('Default recursion limit:', sys.getrecursionlimit()) # 1000
# For large DP problems, increase if needed:
# sys.setrecursionlimit(100000)
# Better: convert to bottom-up DP for large n
def fib_bottom_up(n):
if n <= 1: return n
a, b = 0, 1
for _ in range(2, n+1):
a, b = b, a + b
return b
# No recursion limit issue:
print(fib_bottom_up(10000)) # works fine, no recursionPemeriksaan Cepat
Uji pemahaman Anda tentang konsep Struktur Data & Algoritma — Persiapan Wawancara Pemrograman dari pelajaran ini.
Ringkasan Pelajaran
Dalam pelajaran ini Anda mempelajari: DP dari atas ke bawah dengan kamus memo dan dekorator @lru_cache, solusi bermemoisasi untuk Fibonacci, penukaran koin, LCS, jumlah target, dan pemisahan kata, serta kapan memilih pendekatan dari atas ke bawah dibandingkan dari bawah ke atas. Berikutnya kita mengimplementasikan DP dari bawah ke atas dengan tabulasi dan optimisasi ruang.
Pertanyaan yang Sering Diajukan
Apakah pelajaran “DP Top-Down dengan Memoization” gratis?
Ya — teks lengkap “DP Top-Down dengan Memoization” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus Coding Interview Prep, upgrade ke CoddyKit PRO. Kursus Coding Interview Prep mencakup 4 pelajaran total.
Apa yang akan aku pelajari di “DP Top-Down dengan Memoization”?
Tambahkan dict memo ke solusi rekursif untuk memangkas pemanggilan duplikat, lalu gunakan @lru_cache untuk melakukan memoization dengan kode minimal. Kamu berlatih Coding 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 Coding Interview Prep?
Tidak diperlukan pengalaman sebelumnya. Coding 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 2 dari 4.
Berapa lama pelajaran “DP Top-Down dengan Memoization” 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 Coding Interview Prep ini?
Ya. Setiap pelajaran Coding 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
- Mengenali DP: Submasalah yang Saling Tumpang Tindih
- DP Top-Down dengan Memoization
- DP Bottom-Up dengan Tabulasi
- Coin Change dan Tangga Berbiaya Minimum