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, currsizeYukarı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+1K 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 charsMemo 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')) # 3Yukarı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)) # 1Yukarı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 recursionKı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
- DP’yi Tanıma: Çakışan Alt Problemler
- Not Almayla Yukarıdan Aşağı DP
- Tablolamayla Aşağıdan Yukarı DP
- Madeni Para Değişimi ve Minimum Maliyetli Merdiven