Optimasi Ruang untuk DP 2D
Kurangi ruang LCS dan jarak edit dari O(mn) menjadi O(min(m,n)) dengan hanya mempertahankan baris tabel DP saat ini dan sebelumnya.
Optimasi Ruang untuk DP 2D adalah pelajaran Coding 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 Coding Interview Prep, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus Coding Interview Prep mencakup 4 pelajaran total.
Mengapa Ruang Penting dalam DP 2D
Tabel DP 2D untuk teks dengan panjang 1000 memerlukan 1000×1000 = 1.000.000 sel — sekitar 8 MB untuk bilangan bulat 64-bit. Untuk urutan yang lebih panjang (penyelarasan DNA, diff teks besar), hal ini menjadi tidak praktis. Pengamatan utamanya adalah bahwa sebagian besar relasi rekurensi DP 2D hanya melihat baris saat ini dan baris sebelumnya, sehingga seluruh tabel dapat dipadatkan menjadi satu atau dua larik 1D. Inilah inti optimasi ruang DP 2D.
# 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')Pola Larik Bergulir
Pola larik bergulir menggantikan tabel 2D lengkap dengan larik 1D yang merepresentasikan baris sebelumnya. Saat menghitung baris i, Anda memperbarui setiap sel j menggunakan nilai saat ini dp[j] (yang masih menyimpan dp[i-1][j] dari baris sebelumnya) dan dp[j-1] yang baru diperbarui (yaitu dp[i][j-1]). Variabel diagonal menangkap dp[i-1][j-1] sebelum nilainya ditimpa. Pola ini berlaku untuk LCS, jarak edit, dan sebagian besar masalah DP 2D.
# 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]LCS dengan Ruang O(min m,n)
Untuk LCS, pastikan text1 adalah teks yang lebih pendek (sehingga n bernilai kecil). Alokasikan larik 1D berukuran n+1. Proses baris demi baris. Pada setiap sel: simpan temp = dp[j] (ini adalah dp[i-1][j]). Kemudian: jika karakter cocok, dp[j] = diag + 1; jika tidak, dp[j] = max(dp[j], dp[j-1]). Terakhir, tetapkan diag = temp. Setelah semua baris selesai diproses, dp[n] menyimpan panjang LCS.
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')) # 4Jarak Edit dengan Ruang O(n)
Jarak edit menggunakan pola larik bergulir yang sama. Larik 1D awal merepresentasikan baris 0: dp[j] = j (menyisipkan j karakter). Untuk setiap baris i, tetapkan dp[0] = i (menghapus i karakter) dan simpan diag = dp[0] sebelum pembaruan. Dalam loop bagian dalam, simpan temp = dp[j], hitung nilai baru dari penyisipan (dp[j-1]+1), penghapusan (dp[j]+1), dan penggantian (diag + cost), lalu tetapkan diag = temp.
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')) # 5Jumlah Jalur Minimum dengan Ruang O(n)
Untuk Jumlah Jalur Minimum pada kisi, larik bergulir 1D dimulai sebagai jumlah prefiks baris pertama (hanya ada satu cara untuk mencapai setiap sel pada baris pertama). Untuk setiap baris berikutnya, lakukan pembaruan dari kiri ke kanan: dp[j] sebelum diperbarui adalah nilai dari baris di atasnya (dp[i-1][j]), sedangkan dp[j-1] yang baru diperbarui berasal dari kiri. Diagonal tidak diperlukan di sini karena jumlah jalur minimum tidak memerlukan sel diagonal.
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)) # 7Saat Akses Diagonal Diperlukan
Tidak semua masalah DP 2D dapat dipadatkan dengan larik bergulir sederhana karena beberapa di antaranya memerlukan elemen diagonal dp[i-1][j-1] setelah dp[j] ditimpa. Solusinya selalu sama: simpan temp = dp[j] sebelum memperbaruinya, lalu gunakan sebagai diag untuk perhitungan kolom berikutnya. Cara melihat satu sel ke depan ini menangani rekurensi tiga arah seperti LCS dan jarak penyuntingan dengan rapi.
# 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')) # 4Optimasi Ruang Ransel 2D
Masalah Ransel 0/1 juga mendapat manfaat dari optimasi ruang. Tabel 2D lengkap memiliki dimensi (jumlah item+1) × (kapasitas+1). Larik bergulir menguranginya menjadi O(kapasitas). Perbedaan penting dari LCS dan jarak penyuntingan adalah: iterasikan dimensi kapasitas secara terbalik (dari tinggi ke rendah). Ini memastikan setiap item dihitung paling banyak satu kali—iterasi maju dapat menyebabkan sebuah item dipilih berkali-kali.
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)Iterasi Maju vs Terbalik
Mengetahui arah iterasi perulangan bagian dalam sangat penting: terbalik untuk ransel 0/1 (setiap item digunakan paling banyak satu kali—melihat kembali keadaan sebelumnya mencegah penggunaan ulang). Maju untuk ransel tak terbatas (setiap item dapat digunakan kembali—melihat keadaan yang sudah diperbarui memungkinkan penggunaan berkali-kali). Kesalahan dalam hal ini diam-diam mengubah ransel 0/1 menjadi ransel tak terbatas, atau sebaliknya. Selalu pastikan batasannya sebelum memilih arah iterasi.
# 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+... )Jalur Unik dengan Ruang O(n)
Untuk jalur unik, seluruh tabel dapat digantikan oleh satu baris. Inisialisasikan semua sel dengan 1 (baris pertama). Untuk setiap baris berikutnya, lakukan pembaruan dari kiri ke kanan: dp[j] += dp[j-1]. Diagonal tidak diperlukan karena rekurensinya hanya menggunakan sel di atas (dp[j], nilai saat ini sebelum diperbarui) dan sel di kiri (dp[j-1], yang sudah diperbarui). Ini adalah kompresi 2D→1D yang paling sederhana.
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]])) # 2Penyangga Dua Baris untuk Rekurensi Kompleks
Saat rekurensi memerlukan sel dari dua baris sebelumnya atau lebih (misalnya beberapa varian DP interval atau reduksi DP 3D), gunakan penyangga dua baris: pertahankan larik prev dan curr, lalu tukar keduanya setelah setiap baris. Dengan demikian, ruang O(2n) = O(n) tercapai. Untuk rekurensi yang melihat kembali k baris, pertahankan k larik sebagai penyangga melingkar. Ini merupakan generalisasi pola larik bergulir satu baris.
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')) # 4Saat Optimasi Ruang Tidak Memungkinkan
Optimasi ruang tidak selalu memungkinkan. Jika Anda perlu merekonstruksi hasil optimal (bukan hanya nilainya), umumnya Anda memerlukan tabel lengkap untuk penelusuran mundur. Solusi alternatifnya meliputi: (1) Menyimpan tabel keputusan terpisah dengan ukuran yang sama. (2) Menggunakan algoritma Hirschberg, yang menghitung LCS dalam waktu O(mn) dan ruang O(min(m,n)), termasuk rekonstruksi, dengan membagi masalah secara rekursif di titik tengah. (3) Menerima penggunaan ruang O(mn) saat rekonstruksi diperlukan.
# 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')Pemeriksaan Singkat
Uji pemahaman Anda tentang konsep Struktur Data & Algoritma — Persiapan Wawancara Pemrograman dari pelajaran ini.
Rangkuman Pelajaran
Dalam pelajaran ini Anda telah mempelajari: tabel DP 2D dapat dipadatkan menjadi ruang O(n) menggunakan larik 1D bergulir ketika hanya baris sebelumnya yang diperlukan, pola variabel diagonal (simpan nilai sementara sebelum menimpa) menangani rekurensi yang memerlukan dp[i-1][j-1], dan ransel 0/1 mengiterasikan kapasitas secara terbalik, sedangkan ransel tak terbatas mengiterasikannya secara maju. Selanjutnya kita akan mempelajari templat Penelusuran Mundur: Pilih, Jelajahi, Batalkan Pilihan—dasar algoritma pencarian menyeluruh.
Pertanyaan yang Sering Diajukan
Apakah pelajaran “Optimasi Ruang untuk DP 2D” gratis?
Ya — teks lengkap “Optimasi Ruang untuk DP 2D” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus Coding Interview Prep, upgrade ke CoddyKit PRO. Kursus Coding Interview Prep mencakup 4 pelajaran total.
Apa yang akan aku pelajari di “Optimasi Ruang untuk DP 2D”?
Kurangi ruang LCS dan jarak edit dari O(mn) menjadi O(min(m,n)) dengan hanya mempertahankan baris tabel DP saat ini dan sebelumnya. Kamu berlatih Coding 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 Coding Interview Prep?
Tidak diperlukan pengalaman sebelumnya. Coding 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 “Optimasi Ruang untuk DP 2D” 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 Coding Interview Prep ini?
Ya. Setiap pelajaran Coding 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
- Jalur Unik dan Jumlah Jalur Minimum pada Grid
- Longest Common Subsequence
- Jarak Edit (Levenshtein)
- Optimasi Ruang untuk DP 2D