DP’yi Tanıma: Çakışan Alt Problemler
Kaba kuvvet özyinelemesinin aynı alt problemi yeniden çözdüğü durumları belirleyin, Fibonacci için özyineleme ağacını çizin ve üstel büyümeyi gözlemleyin.
DP’yi Tanıma: Çakışan Alt Problemler, CoddyKit'te ücretsiz bir DSA Interview Prep dersidir. Bu, 4 dersinin 1. 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, DSA Interview Prep öğrenme yolunun bir parçasıdır ve ilerlemeniz web ve CoddyKit uygulaması arasında senkronize olur. DSA Interview Prep kursu toplamda 4 dersten oluşur.
Dinamik Programlama Nedir?
Dinamik programlama (DP), karmaşık problemleri daha basit ve örtüşen alt problemlere bölerek, her alt problemi bir kez çözüp sonucu saklayarak gereksiz hesaplamaları önler. DP, bir problemin iki bileşeni olduğunda uygulanır: örtüşen alt problemler (aynı alt problemin naif özyinelemede birden çok kez çözülmesi) ve optimal alt yapı (en iyi çözümün alt problemlerin en iyi çözümlerinden oluşturulabilmesi). Bu iki bileşen birlikte yoksa DP yardımcı olmaz.
# Two ingredients of DP:
# 1. Overlapping sub-problems:
# fib(5) -> fib(4) + fib(3)
# fib(4) -> fib(3) + fib(2) <- fib(3) computed twice!
# Without caching: O(2^n) calls for Fibonacci
# 2. Optimal substructure:
# Shortest path from A to C through B:
# shortest(A,C) = shortest(A,B) + shortest(B,C)
# The sub-path A->B must itself be the shortest
# Contrast with greedy: greedy makes one locally optimal
# choice; DP tries all choices and picks the best.
print('DP = overlapping sub-problems + optimal substructure')Fibonacci: Klasik DP Başlangıç Noktası
Fibonacci dizisi (fib(n) = fib(n-1) + fib(n-2)), örtüşen alt problemlerin temel örneğidir. Naif özyinelemenin zaman karmaşıklığı O(2^n)'dir; çünkü aynı değerleri tekrar tekrar hesaplar. fib(6) için özyineleme ağacı, fib(3)'ün 3 kez, fib(2)'nin 5 kez ve benzer şekilde hesaplandığını gösterir. Bu üstel büyüme, hesaplanan sonuçlar saklanarak DP'nin ortadan kaldırdığı tam sorundur.
import time
def fib_naive(n):
if n <= 1:
return n
return fib_naive(n-1) + fib_naive(n-2)
# Count the calls:
call_count = [0]
def fib_count(n):
call_count[0] += 1
if n <= 1: return n
return fib_count(n-1) + fib_count(n-2)
fib_count(10)
print(f'Calls for fib(10): {call_count[0]}') # 177 calls for n=10!
call_count[0] = 0
fib_count(20)
print(f'Calls for fib(20): {call_count[0]}') # 21891 calls
# n=30 -> ~2.7 million calls: exponential growthÖzyineleme Ağacını Görselleştirme
fib(5) için özyineleme ağacını çizmek, israfı ortaya çıkarır: her düğüm iki çocuk oluşturur ve özdeş alt ağaçlar tekrar tekrar görünür. Ağaçtaki toplam düğüm sayısı O(2^n)'dir. Aynı bağımsız değişkenlerle yapılan özdeş işlev çağrılarının ağaçta tekrarlandığını gördüğünüzde bu, sonuçları önbelleğe alarak DP'den yararlanabileceğinizi gösterir. Bu görselleştirme becerisi kritik öneme sahiptir: tekrarlanan alt ağaçları belirleyebiliyorsanız DP'nin uygulanabilir olduğunu bilirsiniz.
# fib(5) recursion tree (simplified):
# fib(5)
# / \
# fib(4) fib(3)
# / \ / \
# fib(3) fib(2) fib(2) fib(1)
# / \ \
# fib(2) fib(1) fib(1)
# / \
# fib(1) fib(0)
# fib(3) appears TWICE
# fib(2) appears THREE TIMES
# Each redundant call wastes exponential time
# Key insight: fib(n) only has O(n) DISTINCT sub-problems
# (fib(0), fib(1), ..., fib(n))
# DP computes each ONCE -> O(n) total
print('Distinct sub-problems: O(n) but naive calls: O(2^n)')Örtüşen Alt Problemleri Belirleme
Örtüşen alt problemleri tanımak için kaba kuvvet özyinelemesini yazın, ardından 'aynı bağımsız değişkenlere sahip birden çok özyinelemeli çağrı var mı?' diye sorun. Yanıt evetse DP yardımcı olabilir. Problem açıklamalarındaki yaygın işaretler şunlardır: 'X'in minimum/maksimum sayısı', 'Y için kaç yol var', 'Z'ye ulaşabilir miyiz?'. Bu ifade kalıpları neredeyse her zaman, i konumundaki cevabın daha önceki konumlardaki cevaplara bağlı olduğu bir optimal alt yapı problemine işaret eder.
# DP signal phrases in problem statements:
# 'minimum number of coins to make amount X'
# 'maximum profit from stock trades'
# 'number of ways to climb n stairs'
# 'can you reach the last index?'
# 'longest common subsequence'
# 'edit distance between two strings'
# All have this shape:
# solve(input) = f(solve(smaller_input_1), solve(smaller_input_2), ...)
# And multiple branches end up calling solve with the same argument.
# If the recursion tree has repeated nodes: DP
# If subproblems are all independent: divide-and-conquer (no DP needed)
print('Repeated arguments in recursion tree -> DP')Optimal Alt Yapı Açıklaması
Optimal alt yapı, problemin en iyi çözümünün alt problemlerin en iyi çözümlerinden oluşturulabilmesi anlamına gelir. Örneğin, B üzerinden A'dan C'ye giden en kısa yol, ancak A→B ve B→C alt yollarının her biri kendi başına en kısaysa en iyidir. Bu özellik geçerliyse küresel en iyi çözümü yerel en iyilerden aşağıdan yukarıya doğru oluşturabilirsiniz. En iyi alt yapıya sahip olmayan problemler (örneğin döngüler içeren genel bir çizgede en uzun yol) DP ile çözülemez.
# Optimal substructure examples:
# SHORTEST PATH: shortest(A,C) = min over all B: shortest(A,B) + w(B,C)
# -> Sub-paths must be optimal: YES, has optimal substructure
# LONGEST PATH (no cycles, DAG): can also use DP
# -> Longer path through node B means sub-path A->B must be longest
# LONGEST PATH (with cycles): NO optimal substructure
# -> Best path from A to C might reuse nodes: sub-problems not independent
# COIN CHANGE: min coins for amount n = 1 + min(min coins for n-coin_i)
# -> YES: optimal for n-coin_i is needed for optimal n
print('Optimal substructure: build global optimum from local optima')Merdiven Çıkma: İlk DP'niz
Merdiven Çıkma (LeetCode #70): her seferinde 1 veya 2 basamak çıkarak n basamağı kaç farklı şekilde çıkabilirsiniz? dp[i] = i. basamağa ulaşma yollarının sayısı olsun. i. basamağa i-1. basamaktan (bir adım) veya i-2. basamaktan (iki adım) ulaşabilirsiniz; bu nedenle dp[i] = dp[i-1] + dp[i-2] olur. Bu Fibonacci'dir! Taban durumları: dp[1] = 1, dp[2] = 2. 'Merdiven çıkma'nın Fibonacci'ye indirgendiğini fark etmek, klasik bir mülakat içgörüsüdür.
def climb_stairs(n):
if n <= 2:
return n
dp = [0] * (n + 1)
dp[1] = 1 # 1 way to reach step 1
dp[2] = 2 # 2 ways to reach step 2: (1+1) or (2)
for i in range(3, n + 1):
dp[i] = dp[i-1] + dp[i-2] # come from i-1 or i-2
return dp[n]
for n in range(1, 8):
print(f'climb_stairs({n}) = {climb_stairs(n)}')
# 1, 2, 3, 5, 8, 13, 21 -- Fibonacci sequence!DP Çerçevesi: Tanımlayın, Bağıntıyı Kurun, Sıralayın
Güvenilir bir 3 adımlı DP çerçevesi: 1. Durumu tanımlayın — dp[i] (veya dp[i][j]) neyi temsil ediyor? Bunu İngilizce yazın. 2. Özyineleme bağıntısını yazın — dp[i] değerini daha küçük alt problemlere göre ifade edin. Tüm durumları dahil edin. 3. Doldurma sırasını belirleyin — dp[i] hesaplanmadan önce dp[i-1] değerinin (ve diğer bağımlılıkların) hesaplandığından emin olun. Temel durumlar sınır değerlerini başlatır. Bu çerçeve, belirsiz DP sezgisini somut bir uygulama planına dönüştürür.
# Framework applied to climbing stairs:
# Step 1 - Define state:
# dp[i] = number of distinct ways to reach step i
# Step 2 - Recurrence:
# dp[i] = dp[i-1] + dp[i-2] (come from step i-1 or i-2)
# Step 3 - Fill order:
# Compute dp[1], dp[2], dp[3], ..., dp[n] in order
# Because dp[i] depends on dp[i-1] and dp[i-2] (smaller)
# Base cases: dp[1]=1, dp[2]=2
# Framework applied to coin change:
# Step 1: dp[amount] = minimum coins to make that amount
# Step 2: dp[i] = 1 + min(dp[i-coin] for coin in coins if i >= coin)
# Step 3: Fill i from 1 to amount
# Base: dp[0] = 0 (zero coins for zero amount)
print('DP framework: define state -> recurrence -> fill order')DP Kullanılmaması Gereken Durumlar (NOT)
DP her zaman doğru cevap değildir. Tek bir yerel olarak en iyi seçimin her zaman küresel olarak en iyi çözüme götürdüğü durumlarda açgözlü yaklaşımı kullanın (etkinlik seçimi, sıçrama oyunu I). Alt problemlerin çakışmadığı durumlarda böl ve yönet yaklaşımını kullanın (birleştirmeli sıralama, ikili arama). Problem, ağırlıksız bir grafikte en kısa yolu bulmaksa BFS kullanın. Açgözlü veya daha basit bir yaklaşım varken DP doğru olsa da çoğu zaman gereğinden karmaşıktır. Mülakatlarda, alternatifler yerine neden DP'yi seçtiğinizi açıklayın.
# DP vs alternatives:
# Problem: can you jump to the end of the array?
# Greedy: track max reachable index -> O(n) O(1) BETTER than DP
# Problem: shortest path unweighted graph?
# BFS: O(V+E) BETTER than DP on general graph
# Problem: sort an array?
# Comparison sort: O(n log n), no DP needed
# DP IS the right choice when:
# - Greedy fails (choices interact)
# - Need to count/enumerate all possibilities
# - Problem has 'how many ways' or 'minimum/maximum' flavor
# - Recursion tree clearly shows overlapping sub-problems
print('Ask: does greedy fail? If yes, consider DP.')Farklı Alt Problemleri Sayma
Farklı alt problemlerin sayısı, DP'nin zaman ve bellek karmaşıklığını belirler. Boyutu n olan bir girdi için 1B DP'de O(n) alt problem vardır. Boyutları m ve n olan iki girdi için 2B DP'de O(mn) alt problem vardır. Her alt problem O(k) zamanda çözülürse (her adımda k seçenek için), toplam zaman O(n*k) veya O(mn*k) olur. Önce her zaman farklı alt problemleri sayın — böylece daha kodlamaya başlamadan DP'nin zaman karmaşıklığını elde edersiniz.
# Sub-problem count examples:
# Problem | Sub-problems | Each costs | Total
# Fibonacci | O(n) | O(1) | O(n)
# Coin change | O(amount) | O(coins) | O(amount * coins)
# LCS (m,n chars) | O(m*n) | O(1) | O(m*n)
# Edit distance | O(m*n) | O(1) | O(m*n)
# 0/1 Knapsack | O(n*W) | O(1) | O(n*W)
# Matrix chain | O(n^2) | O(n) | O(n^3)
# Rule: DP time = (# distinct sub-problems) * (time per sub-problem)
print('Time = subproblems * work-per-subproblem')Ev Soyguncusu: Çakışan Seçimler
Ev Soyguncusu (LeetCode #198), bir sıradaki komşu evleri soymadan evlerden elde edebileceğiniz en yüksek miktarı bulmanızı ister. Her evde iki seçeneğiniz vardır: evi soyun (değerini ekleyip önceki evi atlayın) veya pas geçin (önceki evler için bulunan en iyi sonucu alın). dp[i] = max(dp[i-1], dp[i-2] + nums[i]). Her adımda seçim yapılan bu örüntü, en basit 1B DP özyineleme bağıntısıdır ve onlarca mülakat probleminde karşınıza çıkar.
def rob(nums):
if not nums: return 0
if len(nums) == 1: return nums[0]
dp = [0] * len(nums)
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
for i in range(2, len(nums)):
dp[i] = max(dp[i-1], # skip house i
dp[i-2] + nums[i]) # rob house i
return dp[-1]
print(rob([1, 2, 3, 1])) # 4: rob house 0 and 2 (1+3)
print(rob([2, 7, 9, 3, 1]))# 12: rob house 0, 2, 4 (2+9+1)
print(rob([2, 1, 1, 2])) # 4: rob house 0 and 3Sağlama: Kaba Kuvvet ve DP Karşılaştırması
DP'nizi küçük girdiler üzerinde her zaman bir kaba kuvvet çözümüyle doğrulayın. Kaba kuvvet çözümünüz temel gerçektir. DP tüm sınama durumlarında kaba kuvvet çözümüyle eşleştiğinde, özyineleme bağıntısının doğru olduğunu bilirsiniz. Bellek kullanımını optimize etmeye ancak bundan sonra geçin. Sınama odaklı bu yaklaşım — kaba kuvvet → yukarıdan aşağıya DP → aşağıdan yukarıya DP → bellek için optimize edilmiş DP — bir mülakat sırasında DP çözümlerini geliştirmenin ve doğrulamanın profesyonel yoludur.
# Brute-force for house robber (exponential)
def rob_brute(nums, i=0):
if i >= len(nums):
return 0
# Option 1: rob house i
rob_it = nums[i] + rob_brute(nums, i + 2)
# Option 2: skip house i
skip_it = rob_brute(nums, i + 1)
return max(rob_it, skip_it)
# Verify on small inputs:
test_cases = [[1,2,3,1], [2,7,9,3,1], [2,1,1,2]]
for tc in test_cases:
bf = rob_brute(tc)
dp = rob(tc)
print(f'{tc}: brute={bf}, dp={dp}, match={bf==dp}')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: DP'nin iki bileşenini (çakışan alt problemler ve optimal alt yapı), tekrarlanan çağrıları belirlemek için özyineleme ağacını görselleştirmeyi, üç adımlı DP çerçevesini (durumu tanımlama, özyineleme bağıntısı, doldurma sırası) ve Fibonacci, merdiven çıkma ve ev soyguncusu gibi ilk örnekleri. Sırada, önbelleğe alma kullanarak yukarıdan aşağıya DP'yi uygulayacağız.
Sıkça Sorulan Sorular
“DP’yi Tanıma: Çakışan Alt Problemler” dersi ücretsiz mi?
Evet — “DP’yi Tanıma: Çakışan Alt Problemler” 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 DSA Interview Prep kursunun geri kalanını açmak için CoddyKit PRO'ya yükselt. DSA Interview Prep kursu toplamda 4 dersten oluşur.
“DP’yi Tanıma: Çakışan Alt Problemler” dersinde ne öğreneceğim?
Kaba kuvvet özyinelemesinin aynı alt problemi yeniden çözdüğü durumları belirleyin, Fibonacci için özyineleme ağacını çizin ve üstel büyümeyi gözlemleyin. DSA 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.
DSA Interview Prep öğrenmeye başlamak için deneyim gerekli mi?
Önceden deneyim gerekmez. CoddyKit'te DSA 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 1. dersidir.
“DP’yi Tanıma: Çakışan Alt Problemler” 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 DSA Interview Prep dersinde kod yazıp çalıştırabilir miyim?
Evet. Her DSA 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