0Pricing
Coding Interview Prep · Ders

Not Almayla Yukarıdan Aşağı DP

Yinelenen çağrıları budamak için özyinelemeli çözüme bir not sözlüğü ekleyin ve en az kodla not almak için @lru_cache kullanın.

Not Almayla Yukarıdan Aşağı DP, CoddyKit'te ücretsiz bir Coding Interview Prep dersidir. Bu, 4 dersinin 2. dersidir. Aşağıdan dersin tamamını ücretsiz okuyabilir, sonra tarayıcıda yerleşik kod editörü ve 7/24 yapay zeka koçu ile uygulamalı olarak pratik yapabilirsin. Bu, Coding Interview Prep öğrenme yolunun bir parçasıdır ve ilerlemeniz web ve CoddyKit uygulaması arasında senkronize olur. Coding Interview Prep kursu toplamda 4 dersten oluşur.

Yukarıdan Aşağıya DP: Önbelleğe Alma Fikri

Yukarıdan aşağıya DP, özgün özyinelemeli çözümle başlar ve buna önbelleğe alma ekler: her alt problemin sonucunu ilk hesaplandığında saklayan bir önbellek. Aynı parametrelerle yapılan sonraki çağrılarda, özyinelemeye başvurmadan önbellekteki sonuç hemen döndürülür. Bu yaklaşım, çok az kod değişikliğiyle O(2^n) karmaşıklığındaki naif özyinelemeyi O(n) karmaşıklığına dönüştürür — çoğu zaman mevcut özyinelemeli çözüme yalnızca 2-3 satır eklemek yeterlidir.

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

Önbelleğe Alınmış Fibonacci

Naif Fibonacci özyinelemesine bir önbellek sözlüğü eklemek, zamanı O(2^n)'den O(n)'e düşürür. fib(k) için yapılan ilk çağrı sonucu hesaplar ve saklar. Aynı k değeri için yapılan sonraki tüm çağrılar, önbelleğe alınmış değeri anında döndürür. Bellek karmaşıklığı, önbellek sözlüğü için O(n) ve çağrı yığını için O(n)'dir. Çağrı sayılarını karşılaştırın: önbellek olmadan fib(30) yaklaşık 2 milyon çağrı yapar; önbellekle tam 30 çağrı yapılır.

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 once

@functools.lru_cache Kullanımı

Python'un @functools.lru_cache(maxsize=None) dekoratörü (veya Python 3.9 ve sonrasındaki @cache takma adı), bir işlevi parametrelerine göre otomatik olarak önbelleğe alır. Mülakat ortamlarında yukarıdan aşağıya DP eklemenin en temiz yolu budur — özyinelemeli çözümü yazın, dekoratörü ekleyin, işlem tamam. Dekoratör, tüm sonuçları işlevin parametrelerini anahtar olarak kullanan bir sözlükte saklar; bu parametrelerin karma değeri alınabilir olması gerekir (liste kullanmayın — onun yerine demet kullanın).

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, currsize

Yukarıdan Aşağıya Madeni Para Değişimi

Madeni Para Değişimi (LeetCode #322): madeni para kupürleri ve bir hedef miktar verildiğinde, gereken en az madeni para sayısını bulun. Özyinelemeli formülasyonda her madeni parayı sırayla seçip kalan miktar için çözüm bulun ve ardından minimumu alın. Yeniden hesaplamayı önlemek için miktarı önbelleğe alın. Temel durum şudur: miktar=0 için 0 madeni para gerekir; imkânsız bir miktar sonsuz döndürür (veya özyinelemeden sonra -1 döndürülür).

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+1

K Adımlı Yukarıdan Aşağıya Merdiven Çıkma

Merdiven çıkma problemini, 1 ile k arasında adım atılmasına izin verecek şekilde genelleştirin. Durum mevcut basamaktır ve i. basamaktan i+1, i+2, ..., i+k basamaklarına ulaşabilirsiniz. Özyineleme bağıntısı: dp(i) = sum of dp(i-j) for j in 1..k if i-j >= 0. Önbelleğe alma, karmaşıklığı O(k^n) yerine O(n*k) yapar. Bu genelleme, “son basamağa ulaşmanın minimum maliyeti” ve “bir ızgarayı doldurma yollarını sayma” gibi problemlerde karşınıza çıkar.

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]

Yukarıdan Aşağıya LCS: 2B Önbelleğe Alma

En Uzun Ortak Alt Dizi (LCS) için 2B bir durum gerekir: dp(i, j) = s1[:i] ve s2[:j] dizilerinin LCS uzunluğu. s1[i-1] == s2[j-1] ise karakterler eşleşir: dp(i,j) = 1 + dp(i-1, j-1). Aksi durumda: dp(i,j) = max(dp(i-1,j), dp(i,j-1)) — dizelerden birindeki bir karakteri atlayın. (i, j) değerlerine göre önbelleğe almak, karmaşıklığı O(2^(m+n)) yerine O(mn) yapar.

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 chars

Memo Sözlüğü ile lru_cache: Hangisini Seçmeli

İşlev parametreleriniz karma değeri alınabilir temel türlerse (int, str, tuple) @lru_cache kullanın. Şu durumlarda elle tutulan bir önbellek sözlüğü kullanın: değiştirilebilir durumu (liste ve sözlükleri) demetlere dönüştürerek aktarmanız gerekiyorsa, hangi anahtarların hesaplandığını izlemeniz gerekiyorsa veya bir sınıf metodunda self değerinin önbelleğe alınmasını istemiyorsanız. Elle tutulan önbellek sözlüğü daha açıktır ve özyinelemeli yardımcı işlevlerdeki fark edilmesi zor kapsam sorunlarını önler.

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

Yukarıdan Aşağıya Hedef Toplam

Hedef Toplam (LeetCode #494): her sayıya + veya - işareti atayın ve hedef toplamı oluşturan atamaların sayısını bulun. Durum: dp(index, current_sum). Her dizinde, mevcut sayıyı eklemeyi (+) ve çıkarmayı (-) deneyin. (index, current_sum) değerlerine göre önbelleğe almak, O(2^n) karmaşıklığındaki kaba kuvvet çözümünü O(n * sum_range) karmaşıklığına dönüştürür. Toplam aralığı tüm sayıların toplamıyla sınırlıdır; böylece toplam durum sayısı O(n * S) olur.

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

Yukarıdan Aşağıya ve Aşağıdan Yukarıya: Artıları ve Eksileri

Yukarıdan aşağıya (önbelleğe alma) avantajları: yazması doğaldır (özyinelemeli çözümle başlar), yalnızca gerçekten gereken alt problemleri hesaplar (tembel hesaplama) ve önbelleği aşamalı olarak eklemek kolaydır. Aşağıdan yukarıya (tablolama) avantajları: çağrı yığını ek yükü yoktur (Python özyineleme sınırı yoktur), bellek erişimi önbellek açısından daha verimlidir ve bellek kullanımını optimize etmek daha kolaydır. Her ikisinin de asimptotik karmaşıklığı aynıdır. Mülakatlarda doğruluğu doğrulamak için yukarıdan aşağıya başlayın; daha iyi bellek kullanımı istenirse aşağıdan yukarıya dönüştürün.

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

Yukarıdan Aşağıya DP ile Kelimelere Ayırma

Kelimelere Ayırma (LeetCode #139), s dizesinin bir sözlükteki kelimelere ayrılıp ayrılamayacağını sorar. Durum: dp(i) = s[i:] ifadesinin kelimelere ayrılıp ayrılamayacağı. i dizininden başlayarak tüm kelimeleri deneyin: s[i:i+len(w)] == w ise kalan son ek üzerinde özyinelemeli çağrı yapın. Başlangıç dizinine göre önbelleğe almak, küme üyeliği denetimiyle O(2^n) karmaşıklığındaki kaba kuvvet çözümünü O(n^2)'ye (veya O(n * max_word_len)'e) dönüştürür.

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

Özyineleme Sınırı ve Yineleme Araçları

Python'un varsayılan özyineleme sınırı 1000'dir ve bu sınır sys.getrecursionlimit() tarafından belirlenir. Büyük girdilerle (n = 10,000+) çalışılan DP problemlerinde yukarıdan aşağıya önbelleğe alma bu sınıra ulaşır. Seçenekleriniz şunlardır: sys.setrecursionlimit(100000) ile sınırı artırmak veya aşağıdan yukarıya DP'ye dönüştürmek. Yarışmalı programlamada sınırı artırmak yaygındır; üretim kodunda ise güvenilirlik için her zaman aşağıdan yukarıya veya yinelemeli çözümleri tercih edin.

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 recursion

Kısa Sınama

Bu dersteki Veri Yapıları & Algoritmalar — Kodlama Mülakatına Hazırlık kavramlarını anlayışınızı sınayın.

Ders Özeti

Bu derste şunları öğrendiniz: önbellek sözlüğü ve @lru_cache dekoratörüyle yukarıdan aşağıya DP'yi, Fibonacci, madeni para değişimi, LCS, hedef toplam ve kelimelere ayırma için önbelleğe alınmış çözümleri ve yukarıdan aşağıya ile aşağıdan yukarıya yaklaşımlardan ne zaman hangisini seçmeniz gerektiğini. Sırada, tablolama ve bellek kullanımını optimize etme yöntemleriyle aşağıdan yukarıya DP'yi uygulayacağız.

Sıkça Sorulan Sorular

“Not Almayla Yukarıdan Aşağı DP” dersi ücretsiz mi?

Evet — “Not Almayla Yukarıdan Aşağı DP” dersin tüm metni burada web'de ücretsiz olarak okunabilir. Etkileşimli olarak pratik yapmak (yerleşik kod editörü ve 7/24 yapay zeka koçu) ve Coding Interview Prep kursunun geri kalanını açmak için CoddyKit PRO'ya yükselt. Coding Interview Prep kursu toplamda 4 dersten oluşur.

“Not Almayla Yukarıdan Aşağı DP” dersinde ne öğreneceğim?

Yinelenen çağrıları budamak için özyinelemeli çözüme bir not sözlüğü ekleyin ve en az kodla not almak için @lru_cache kullanın. Coding Interview Prep ile uygulamalı kodu tarayıcıda doğrudan çalıştırarak pratik yaparsın ve 7/24 yapay zeka koçu dersi çalışırken sorularını yanıtlar.

Coding Interview Prep öğrenmeye başlamak için deneyim gerekli mi?

Önceden deneyim gerekmez. CoddyKit'te Coding Interview Prep, başlangıçtan ileri seviyeye kadar yapılandırıldığı için buradan başlayabilir veya başından başlayıp kendi hızında ilerleme yapabilirsin. Bu, 4 dersinin 2. dersidir.

“Not Almayla Yukarıdan Aşağı DP” dersi ne kadar sürer?

Çoğu CoddyKit dersi yaklaşık 5–10 dakika sürer. Her biri kısa ve etkileşimli olduğu için sabit ilerleme yaparsın ve web ile uygulama arasında tam olarak bıraktığın yerden devam edebilirsin.

Bu Coding Interview Prep dersinde kod yazıp çalıştırabilir miyim?

Evet. Her Coding Interview Prep dersi yerleşik bir kod editörü içerir, bu sayede tarayıcıda gerçek kod yazıp çalıştırabilir ve anlık yapay zeka geri bildirimi alırsın — yerel kurulum gerekli değildir.

Bu kursun tüm dersleri

  1. DP’yi Tanıma: Çakışan Alt Problemler
  2. Not Almayla Yukarıdan Aşağı DP
  3. Tablolamayla Aşağıdan Yukarı DP
  4. Madeni Para Değişimi ve Minimum Maliyetli Merdiven
← Coding Interview Prep Sayfasına Dön