DSA Interview Prep · Pelajaran

Memoization: Menyimpan Hasil Rekursif

Terapkan @functools.lru_cache dan dict memo manual pada Fibonacci serta climbing-stairs untuk menghilangkan perhitungan ulang eksponensial.

Pelajaran 4 dari 413 langkah

Memoization: Menyimpan Hasil Rekursif 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.

Masalah dengan Rekursi yang Berulang

Fibonacci rekursif naif menghitung nilai yang sama berulang kali. fib(5) memanggil fib(4) dan fib(3); fib(4) memanggil fib(3) dan fib(2)—jadi fib(3) dihitung dua kali. Redundansi ini berkembang secara eksponensial: fib(40) melakukan lebih dari satu miliar pemanggilan fungsi. Memoisasi mengatasi hal ini dengan menyimpan setiap hasil saat pertama kali dihitung, sehingga pemanggilan berikutnya mengambilnya dalam O(1), bukan menghitungnya ulang.

# Count calls without memoisation
call_count = [0]

def fib_plain(n):
    call_count[0] += 1
    if n <= 1: return n
    return fib_plain(n-1) + fib_plain(n-2)

fib_plain(20)
print(f'fib(20) without memo: {call_count[0]:,} calls')
# ~21,891 calls for n=20; ~1 billion for n=40

Memoisasi Manual dengan Kamus

Tambahkan kamus memo sebagai parameter (atau penutupan). Sebelum menghitung, periksa apakah jawabannya sudah ada dalam kamus. Jika ya, segera kembalikan. Jika tidak, hitung, simpan dalam kamus, lalu kembalikan. Setiap submasalah unik kini dihitung tepat satu kali, sehingga O(2^n) berubah menjadi waktu O(n) dan ruang O(n) untuk kamus memoisasi, ditambah ruang tumpukan O(n).

def fib_memo(n, memo={}):
    if n in memo:
        return memo[n]
    if n <= 1:
        return n
    memo[n] = fib_memo(n-1, memo) + fib_memo(n-2, memo)
    return memo[n]

print(fib_memo(10))   # 55
print(fib_memo(50))   # 12586269025
print(fib_memo(100))  # huge number — still fast!

Dekorator functools.lru_cache

Python menyediakan @functools.lru_cache(maxsize=None) (juga tersedia sebagai @functools.cache di Python 3.9+) untuk mengotomatiskan memoisasi. Menambahkan dekorator ini di atas sebuah fungsi menyimpan semua pemanggilan berdasarkan argumennya. maxsize=None berarti ukuran tembolok tak terbatas—setiap kombinasi argumen unik disimpan dalam tembolok. Hal ini mengubah fungsi rekursif apa pun menjadi versi yang dimemoisasi hanya dengan satu baris kode.

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))  # 354224848179261915075
print(fib.cache_info())  # CacheInfo(hits=..., misses=..., maxsize=None, currsize=...)

Mendaki Tangga (LeetCode 70)

LeetCode 70 'Mendaki Tangga': Anda dapat menaiki 1 atau 2 anak tangga sekaligus. Ada berapa cara untuk mencapai anak tangga ke-n? Ini pada dasarnya adalah Fibonacci: ways(n) = ways(n-1) + ways(n-2). Kasus dasar: ways(0) = 1 (satu cara untuk tetap berada di lantai dasar), ways(1) = 1. Dengan memoisasi, waktunya O(n) dan ruangnya O(n).

import functools

@functools.lru_cache(maxsize=None)
def climbStairs(n):
    if n <= 1:
        return 1
    return climbStairs(n-1) + climbStairs(n-2)

for i in range(1, 8):
    print(f'climbStairs({i}) = {climbStairs(i)}')
# 1,2,3,5,8,13,21

Penukaran Koin (LeetCode 322)

LeetCode 322 'Penukaran Koin': diberikan nilai pecahan koin dan jumlah target, tentukan jumlah minimum koin. Rekursi dimemoisasi dari atas ke bawah: dp(amount) = 1 + min(dp(amount - coin)) untuk setiap koin yang valid. Kasus dasarnya: dp(0) = 0. Simpan setiap subjumlah dalam tembolok. Jika suatu subjumlah tidak mungkin dicapai, kembalikan tak terhingga. Memoisasi mengubah pendekatan coba-coba eksponensial menjadi waktu O(jumlah × panjang(daftar koin)).

import functools

def coinChange(coins, amount):
    @functools.lru_cache(maxsize=None)
    def dp(rem):
        if rem == 0:
            return 0
        if rem < 0:
            return float('inf')
        return 1 + min(dp(rem - c) for c in coins)

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

print(coinChange([1, 5, 11], 15))  # 3 (5+5+5)
print(coinChange([1, 2, 5], 11))   # 3 (5+5+1)
print(coinChange([2], 3))          # -1

Pemisahan Kata (LeetCode 139) dengan Memoisasi

LeetCode 139 'Pemisahan Kata': tentukan apakah sebuah untai dapat dipisahkan menjadi kata-kata dalam kamus. Rekursi dari atas ke bawah: can_break(s, start) mencoba setiap awalan s[start:end]; jika awalan tersebut ada di dalam kamus dan can_break(s, end) bernilai benar, kembalikan benar. Tanpa memoisasi, kompleksitasnya O(2^n); dengan memoisasi (menyimpan setiap indeks awal), kompleksitasnya menjadi O(n² × L), dengan L sebagai panjang kata maksimum.

import functools

def wordBreak(s, wordDict):
    word_set = set(wordDict)

    @functools.lru_cache(maxsize=None)
    def can_break(start):
        if start == len(s):
            return True
        for end in range(start + 1, len(s) + 1):
            if s[start:end] in word_set and can_break(end):
                return True
        return False

    return can_break(0)

print(wordBreak('leetcode', ['leet', 'code']))    # True
print(wordBreak('applepenapple', ['apple','pen'])) # True
print(wordBreak('catsandog', ['cats','dog','sand','and','cat']))  # False

Memoisasi dibandingkan Tabulasi

Memoisasi (dari atas ke bawah) dimulai dari masalah awal dan menyimpan jawaban saat submasalah ditemukan secara rekursif. Pendekatan ini hanya menyelesaikan submasalah yang benar-benar diperlukan. Tabulasi (dari bawah ke atas) terlebih dahulu mengisi tabel dari submasalah kecil ke besar, sehingga semua submasalah diselesaikan tanpa memandang kebutuhannya. Memoisasi lebih mudah diturunkan dari solusi rekursif; tabulasi menghindari keterbatasan kedalaman rekursi dan beban pemanggilan fungsi.

# Memoisation (top-down)
import functools
@functools.lru_cache(maxsize=None)
def fib_td(n):
    if n <= 1: return n
    return fib_td(n-1) + fib_td(n-2)

# Tabulation (bottom-up)
def fib_bu(n):
    if n <= 1: return n
    dp = [0] * (n + 1)
    dp[1] = 1
    for i in range(2, n + 1):
        dp[i] = dp[i-1] + dp[i-2]
    return dp[n]

print(fib_td(20), fib_bu(20))   # 6765 6765
# Both O(n) time; fib_bu avoids recursion limit

Optimasi Ruang: Variabel Bergulir

Banyak masalah DP yang dapat diselesaikan oleh rekursi yang dimemoisasi dalam ruang O(n) dapat lebih dioptimalkan menjadi ruang O(1) ketika hanya diperlukan sejumlah tetap jawaban submasalah sebelumnya. Untuk Fibonacci, hanya dua nilai terakhir yang penting. Untuk mendaki tangga, prinsipnya sama. Menggulirkan dua variabel menggantikan seluruh kamus memoisasi atau tabel.

# Fibonacci with O(1) space
def fib_o1(n):
    if n <= 1:
        return n
    prev2, prev1 = 0, 1
    for _ in range(2, n + 1):
        prev2, prev1 = prev1, prev2 + prev1
    return prev1

for i in range(8):
    print(f'fib({i})={fib_o1(i)}', end='  ')
print()

# Climbing stairs O(1) space
def climbStairs_o1(n):
    if n <= 1: return 1
    a, b = 1, 1
    for _ in range(2, n + 1):
        a, b = b, a + b
    return b
print(climbStairs_o1(10))  # 89

lru_cache dibandingkan Penutupan dan Kamus Global

Ada tiga cara untuk menerapkan memoisasi secara manual. Kamus global sederhana, tetapi mengotori cakupan modul. Penutupan membungkus tembolok di dalam fungsi, sehingga mencegah kebocoran, tetapi memerlukan pembungkus. @lru_cache adalah pilihan yang paling rapi—satu dekorator menggantikan semua kode pendukung. Dalam konteks wawancara, mulailah dengan @lru_cache kecuali pewawancara secara khusus meminta implementasi manual.

import functools

# 1. Global dict (messy)
memo_global = {}
def fib_global(n):
    if n in memo_global: return memo_global[n]
    if n <= 1: return n
    memo_global[n] = fib_global(n-1) + fib_global(n-2)
    return memo_global[n]

# 2. Closure (cleaner scope)
def make_fib():
    cache = {}
    def fib(n):
        if n in cache: return cache[n]
        if n <= 1: return n
        cache[n] = fib(n-1) + fib(n-2)
        return cache[n]
    return fib
fib_closure = make_fib()

# 3. lru_cache (best)
@functools.lru_cache(maxsize=None)
def fib_cached(n):
    if n <= 1: return n
    return fib_cached(n-1) + fib_cached(n-2)

print(fib_global(30), fib_closure(30), fib_cached(30))  # all 832040

Saat Memoisasi Tidak Membantu

Memoisasi hanya mempercepat masalah dengan submasalah yang tumpang tindih—kasus ketika submasalah yang sama dihitung beberapa kali. Jika setiap submasalah unik (seperti dalam penelusuran pohon sederhana ketika setiap simpul dikunjungi tepat satu kali), memoisasi menambah beban tanpa manfaat. Selain itu, memoisasi tidak dapat memperbaiki masalah ketika pohon rekursif bersifat eksponensial dalam jumlah submasalah yang berbeda, bukan karena penggunaan ulang—masalah seperti itu memerlukan algoritma yang sama sekali berbeda.

# Memoisation DOES help: overlapping sub-problems (Fibonacci)
# fib(n) reuses fib(n-2), fib(n-3), etc.

# Memoisation does NOT help: distinct sub-problems (permutations)
# Each unique (remaining_elements, target) pair is truly distinct
# The exponential complexity comes from the state space itself

print('Memoisation: useful when SAME sub-problem recurs multiple times')
print('Not useful: when every sub-problem is unique to one recursive path')

Ringkasan: Daftar Periksa Memoisasi

Terapkan memoisasi ketika: Anda memiliki solusi rekursif yang benar tetapi lambat karena perhitungan ulang yang berlebihan, fungsi memiliki sejumlah kecil kombinasi argumen yang berbeda, dan nilai kembalian hanya bergantung pada argumen (fungsi murni—tanpa efek samping dan tanpa keadaan global). Periksa ruang keadaan submasalah: jika terdapat paling banyak O(n) atau O(n²) keadaan yang berbeda, memoisasi mengubah waktu eksponensial menjadi waktu polinomial.

Pemeriksaan Singkat

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

Rangkuman Pelajaran

Dalam pelajaran ini Anda mempelajari: memoisasi menyimpan hasil submasalah untuk menghindari perhitungan ulang, sehingga mengubah rekursi eksponensial menjadi waktu polinomial, @functools.lru_cache adalah alat Python idiomatis yang hanya memerlukan satu baris, dan memoisasi (dari atas ke bawah) serta tabulasi (dari bawah ke atas) adalah dua gaya DP—memoisasi lebih mudah diturunkan, sedangkan tabulasi menghindari masalah kedalaman tumpukan. Selamat—Anda telah menyelesaikan modul rekursi dan peta hash!

Gratis untuk memulai

Belajar Python dengan tutor AI — gratis

Tulis dan jalankan kode asli di browser kamu, dapatkan bantuan instan dari tutor AI 24/7, dan lanjutkan di mana kamu tinggalkan di web atau aplikasi.

Kursus
30
Pelajaran
120

Pertanyaan yang Sering Diajukan

Apakah pelajaran “Memoization: Menyimpan Hasil Rekursif” gratis?

Ya — teks lengkap “Memoization: Menyimpan Hasil Rekursif” 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 “Memoization: Menyimpan Hasil Rekursif”?

Terapkan @functools.lru_cache dan dict memo manual pada Fibonacci serta climbing-stairs untuk menghilangkan perhitungan ulang eksponensial. 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 “Memoization: Menyimpan Hasil Rekursif” 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. Kerangka Rekursi: Kasus Dasar, Kepercayaan, Pembangunan
  2. Memvisualisasikan Tumpukan Pemanggilan
  3. Pertukaran Rekursif dan Iteratif
  4. Memoization: Menyimpan Hasil Rekursif
← Kembali ke DSA Interview Prep