0Pricing
DSA Interview Prep · Ders

İki Boyutlu DP için Alan Optimizasyonu

DP tablosunun yalnızca geçerli ve önceki satırlarını tutarak LCS ile düzenleme uzaklığının alan kullanımını O(mn)’den O(min(m,n))’e düşürün.

İki Boyutlu DP için Alan Optimizasyonu, CoddyKit'te ücretsiz bir DSA Interview Prep dersidir. Bu, 4 dersinin 4. 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.

2B DP'de Bellek Neden Önemlidir

Uzunluğu 1000 olan dizeler için 2B bir DP tablosu 1000×1000 = 1.000.000 hücre gerektirir; bu, 64 bit tamsayılar için yaklaşık 8 MB demektir. Daha uzun dizilerde (DNA hizalaması, büyük metin diff işlemleri) bu kullanım pratik olmaktan çıkar. Temel gözlem şudur: 2B DP bağıntılarının çoğu yalnızca geçerli ve önceki satıra bakar; bu nedenle tablonun tamamı bir veya iki 1B diziye sıkıştırılabilir. 2B DP bellek optimizasyonunun temelini bu oluşturur.

# Full 2D DP: O(mn) space
# LCS for 1000-char strings
m, n = 1000, 1000
dp_2d_size = m * n * 8  # bytes (64-bit ints)
print(f'2D table: {dp_2d_size:,} bytes = {dp_2d_size//1024} KB')

# 1D rolling array: O(n) space
dp_1d_size = n * 8
print(f'1D array: {dp_1d_size:,} bytes = {dp_1d_size} bytes')
print(f'Space saving: {dp_2d_size // dp_1d_size}x')

Kayan Dizi Deseni

Kayan dizi deseni, tam 2B tablonun yerine önceki satırı temsil eden bir 1B dizi kullanır. i satırı hesaplanırken her j hücresini, hâlâ önceki satırın dp[i-1][j] değerini tutan geçerli dp[j] değeri ve az önce güncellenen dp[j-1] değeri (dp[i][j-1]) ile güncellersiniz. diagonal değişkeni, üzerine yazılmadan önce dp[i-1][j-1] değerini saklar. Bu desen LCS, düzenleme mesafesi ve 2B DP problemlerinin çoğunda uygulanabilir.

# Rolling array template for 2D DP
# Before update: dp[j] holds dp[i-1][j] (previous row)
# After update: dp[j] holds dp[i][j] (current row)

def rolling_array_template(grid):
    m, n = len(grid), len(grid[0])
    dp = [0] * (n + 1)  # represents one row
    for i in range(1, m + 1):
        diag = 0  # stores dp[i-1][j-1] before overwrite
        for j in range(1, n + 1):
            temp = dp[j]  # save dp[i-1][j] before overwriting
            # compute dp[i][j] using dp[j] (above) and dp[j-1] (left) and diag
            dp[j] = diag + dp[j] + dp[j-1]  # placeholder logic
            diag = temp
    return dp[n]

O(min m,n) Bellek Kullanımıyla LCS

LCS için text1'in daha kısa dize olduğundan emin olun (böylece n küçük olur). n+1 boyutunda bir 1B dizi ayırın. Satırları birer birer işleyin. Her hücrede temp = dp[j] değerini kaydedin (bu, dp[i-1][j] değeridir). Ardından karakterler eşleşiyorsa dp[j] = diag + 1, aksi hâlde dp[j] = max(dp[j], dp[j-1]) işlemini yapın. Son olarak diag = temp atamasını gerçekleştirin. Tüm satırlar tamamlandığında dp[n], LCS uzunluğunu içerir.

def lcs_space_opt(text1, text2):
    # Ensure text2 is the shorter one
    if len(text1) < len(text2):
        text1, text2 = text2, text1
    m, n = len(text1), len(text2)
    dp = [0] * (n + 1)
    for i in range(1, m + 1):
        diag = 0
        for j in range(1, n + 1):
            temp = dp[j]  # dp[i-1][j]
            if text1[i-1] == text2[j-1]:
                dp[j] = diag + 1
            else:
                dp[j] = max(dp[j], dp[j-1])
            diag = temp
    return dp[n]

print(lcs_space_opt('ABCBDAB', 'BDCABA'))  # 4
print(lcs_space_opt('AGGTAB', 'GXTXAYB')) # 4

O(n) Bellek Kullanımıyla Düzenleme Mesafesi

Düzenleme mesafesi aynı kayan dizi desenini kullanır. Başlangıçtaki 1B dizi 0. satırı temsil eder: dp[j] = j (j karakter ekleme). Her i satırı için dp[0] = i (i karakter silme) atamasını yapın ve güncellemeden önce diag = dp[0] değerini kaydedin. İç döngüde temp = dp[j] değerini kaydedin, yeni değeri ekleme (dp[j-1]+1), silme (dp[j]+1) ve değiştirme (diag + cost) değerlerinden hesaplayın, ardından diag = temp atamasını yapın.

def edit_dist_opt(s, t):
    m, n = len(s), len(t)
    dp = list(range(n + 1))   # row 0: dp[0][j] = j
    for i in range(1, m + 1):
        diag = dp[0]           # dp[i-1][0] before dp[0] update
        dp[0] = i              # dp[i][0] = i
        for j in range(1, n + 1):
            temp = dp[j]       # dp[i-1][j]
            cost = 0 if s[i-1] == t[j-1] else 1
            dp[j] = min(
                dp[j-1] + 1,  # insert
                dp[j] + 1,    # delete
                diag + cost   # replace or match
            )
            diag = temp
    return dp[n]

print(edit_dist_opt('horse', 'ros'))  # 3
print(edit_dist_opt('intention', 'execution'))  # 5

O(n) Alanla En Küçük Yol Toplamı

Izgara üzerindeki En Küçük Yol Toplamı için 1B kayan dizi, ilk satırın önek toplamları olarak başlar (ilk satırdaki her hücreye ulaşmanın yalnızca bir yolu vardır). Sonraki her satır için soldan sağa güncelleyin: dp[j] güncellemeden önce yukarıdaki satırdaki değerdir (dp[i-1][j]); yeni güncellenmiş dp[j-1] ise soldan gelir. Burada köşegene gerek yoktur; çünkü en küçük yol toplamı köşegen hücreyi gerektirmez.

def min_path_sum_opt(grid):
    m, n = len(grid), len(grid[0])
    dp = [float('inf')] * n
    dp[0] = 0
    for i in range(m):
        # Update first column (only from above)
        dp[0] += grid[i][0]
        for j in range(1, n):
            # min of above (dp[j] = old) and left (dp[j-1] = updated)
            dp[j] = grid[i][j] + min(dp[j], dp[j-1])
    return dp[n-1]

grid = [[1,3,1],[1,5,1],[4,2,1]]
print(min_path_sum_opt(grid))  # 7

Köşegen Erişimi Gerektiğinde

Tüm 2B DP problemleri basit bir kayan diziyle sıkıştırılamaz; çünkü bazıları the köşegen öğeye dp[i-1][j-1], dp[j] üzerine yazıldıktan sonra ihtiyaç duyar. Çözüm her zaman aynıdır: güncellemeden önce temp = dp[j] değerini kaydedin ve bunu sonraki sütunun hesaplamasında diag olarak kullanın. Bu tek hücrelik ileri bakış, üç yönlü bağıntıların (LCS, düzenleme uzaklığı) tümünü temiz biçimde ele alır.

# Recap: the diagonal save pattern
# Without it: dp[j-1] updated (left) and dp[j] about to be overwritten
# With it:

def show_diagonal_pattern(s1, s2):
    n = len(s2)
    dp = [0] * (n + 1)
    for ch1 in s1:
        diag = 0  # was dp[i-1][0] = 0 for LCS
        for j, ch2 in enumerate(s2, 1):
            temp = dp[j]  # SAVE before overwrite
            if ch1 == ch2:
                dp[j] = diag + 1  # use saved diagonal
            else:
                dp[j] = max(dp[j], dp[j-1])
            diag = temp  # advance diagonal
    return dp[n]

print(show_diagonal_pattern('ABCBDAB', 'BDCABA'))  # 4

2B Sırt Çantası Alan Optimizasyonu

0/1 Sırt Çantası problemi de alan optimizasyonundan yararlanır. Tam 2B tablonun boyutları (n_items+1) × (capacity+1) şeklindedir. Kayan dizi bunu O(capacity) değerine indirir. LCS ve düzenleme uzaklığından en önemli fark şudur: kapasite boyutunu tersten yineleyin (yüksek değerden düşük değere). Böylece her öğe en fazla bir kez sayılır; ileri yönde yinelemek bir öğenin birden çok kez seçilmesine izin verebilir.

def knapsack_01(weights, values, capacity):
    dp = [0] * (capacity + 1)
    for w, v in zip(weights, values):
        # Reverse order: prevents using the same item twice
        for c in range(capacity, w - 1, -1):
            dp[c] = max(dp[c], dp[c - w] + v)
    return dp[capacity]

weights = [1, 3, 4, 5]
values  = [1, 4, 5, 7]
cap = 7
print(knapsack_01(weights, values, cap))  # 9 (items 3+4: weight 3+4=7, value 4+5=9)

İleriye ve Tersine İterasyon

İç döngünün hangi yönde yineleneceğini bilmek kritiktir: 0/1 sırt çantası için tersine (her öğe en fazla bir kez kullanılır; önceki durumlara geriden bakmak yeniden kullanımı önler). Sınırsız sırt çantası için ileriye (her öğe yeniden kullanılabilir; zaten güncellenmiş durumlara bakmak birden çok kullanıma izin verir). Bunu yanlış yapmak, 0/1 problemini sessizce sınırsız probleme veya tersine dönüştürür. Yönü seçmeden önce kısıtı her zaman doğrulayın.

# 0/1 Knapsack: each item used AT MOST ONCE → iterate reverse
def knapsack_01_demo(weights, values, cap):
    dp = [0] * (cap + 1)
    for w, v in zip(weights, values):
        for c in range(cap, w-1, -1):  # REVERSE
            dp[c] = max(dp[c], dp[c-w] + v)
    return dp[cap]

# Unbounded Knapsack: items can be reused → iterate forward
def knapsack_unbounded(weights, values, cap):
    dp = [0] * (cap + 1)
    for c in range(1, cap + 1):
        for w, v in zip(weights, values):
            if c >= w:
                dp[c] = max(dp[c], dp[c-w] + v)  # FORWARD
    return dp[cap]

print(knapsack_01_demo([2,3],[3,4],5))     # 7
print(knapsack_unbounded([2,3],[3,4],5))   # 8 (use weight-2 twice: 3+3=6? or 4+... )

O(n) Alanla Benzersiz Yollar

Benzersiz Yollar için tablonun tamamı tek bir satırla değiştirilebilir. Tüm hücreleri 1 olarak başlatın (ilk satır). Sonraki her satır için soldan sağa güncelleyin: dp[j] += dp[j-1]. Köşegene gerek yoktur; çünkü bağıntı yalnızca üstteki hücreyi (dp[j], güncellemeden önceki mevcut değer) ve soldaki hücreyi (dp[j-1], zaten güncellenmiş değer) kullanır. Bu, 2B→1B sıkıştırmanın en basit biçimidir.

def unique_paths_opt(m, n):
    dp = [1] * n  # first row: all 1s
    for i in range(1, m):
        for j in range(1, n):
            dp[j] += dp[j-1]  # above (dp[j]) + left (dp[j-1])
    return dp[n-1]

# With obstacles
def unique_paths_obstacles_opt(grid):
    m, n = len(grid), len(grid[0])
    dp = [0] * n
    dp[0] = 1
    for i in range(m):
        if grid[i][0] == 1: dp[0] = 0  # blocked column
        for j in range(1, n):
            if grid[i][j] == 1: dp[j] = 0  # blocked
            else: dp[j] += dp[j-1]
    return dp[n-1]

print(unique_paths_opt(3, 7))  # 28
print(unique_paths_obstacles_opt([[0,0,0],[0,1,0],[0,0,0]]))  # 2

Karmaşık Bağıntılar için İki Satırlı Tampon

Bağıntı iki veya daha fazla önceki satırdaki hücrelere ihtiyaç duyduğunda (örneğin bazı aralık DP çeşitlemelerinde veya 3B DP indirgemelerinde) iki satırlı tampon kullanılır: prev ve curr dizilerini tutun, her satırdan sonra bunları yer değiştirin. Bu, O(2n) = O(n) alan sağlar. k satır geriye bakan bağıntılar için k diziyi dairesel tampon olarak tutun. Bu yaklaşım, tek satırlı kayan dizi örüntüsünü genelleştirir.

def lcs_two_row_buffer(s1, s2):
    m, n = len(s1), len(s2)
    prev = [0] * (n + 1)  # dp[i-1]
    curr = [0] * (n + 1)  # dp[i]
    for i in range(1, m + 1):
        curr[0] = 0
        for j in range(1, n + 1):
            if s1[i-1] == s2[j-1]:
                curr[j] = prev[j-1] + 1
            else:
                curr[j] = max(prev[j], curr[j-1])
        prev, curr = curr, prev  # swap (curr becomes prev)
    return prev[n]  # after swap, prev holds the last computed row

print(lcs_two_row_buffer('ABCBDAB', 'BDCABA'))  # 4

Alan Optimizasyonu Mümkün Olmadığında

Alan optimizasyonu her zaman mümkün değildir. Yalnızca değerini değil, en iyi solution'ı da yeniden oluşturmanız gerekiyorsa geri izleme için genellikle tam tabloya ihtiyaç duyarsınız. Çözüm yolları şunlardır: (1) Aynı boyutta ayrı bir karar tablosu saklamak. (2) Problemi orta noktada özyinelemeli olarak bölerek yeniden oluşturma da dâhil olmak üzere LCS'yi O(mn) zamanda ve O(min(m,n)) alanda hesaplayan Hirschberg algoritmasını kullanmak. (3) Yeniden oluşturma gerektiğinde O(mn) alanı kabul etmek.

# When reconstruction needed: must keep full table or use Hirschberg
# Hirschberg's idea: compute LCS length in O(n) space at midpoint of s1,
# recurse on left and right halves. O(mn) time, O(n) space + reconstruction.

# For interview: mention the trade-off
# 'I can reduce to O(n) space if only the value is needed.
#  To also reconstruct the sequence, I need the full O(mn) table
#  or a more complex divide-and-conquer approach.'

print('Space opt: O(n) for length only')
print('Full table: O(mn) needed for reconstruction')

Hızlı Kontrol

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: yalnızca önceki satır gerektiğinde 2B DP tabloları kayan 1B dizi kullanılarak O(n) alana sıkıştırılabilir, köşegen değişkeni örüntüsü (üzerine yazmadan önce temp'i kaydetmek) dp[i-1][j-1] gerektiren bağıntıları ele alır ve 0/1 sırt çantası kapasiteyi tersten, sınırsız sırt çantası ise ileriye doğru yineler. Sırada kapsamlı arama algoritmalarının temeli olan Geri İzleme şablonunu (Seç, İncele, Seçimi Geri Al) inceleyeceğiz.

Sıkça Sorulan Sorular

“İki Boyutlu DP için Alan Optimizasyonu” dersi ücretsiz mi?

Evet — “İki Boyutlu DP için Alan Optimizasyonu” 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.

“İki Boyutlu DP için Alan Optimizasyonu” dersinde ne öğreneceğim?

DP tablosunun yalnızca geçerli ve önceki satırlarını tutarak LCS ile düzenleme uzaklığının alan kullanımını O(mn)’den O(min(m,n))’e düşürün. 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 4. dersidir.

“İki Boyutlu DP için Alan Optimizasyonu” 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

  1. Izgaralarda Benzersiz Yollar ve Minimum Yol Toplamı
  2. En Uzun Ortak Alt Dizi
  3. Düzenleme Uzaklığı (Levenshtein)
  4. İki Boyutlu DP için Alan Optimizasyonu
← DSA Interview Prep Sayfasına Dön