Persediaan Temu Duga Pengaturcaraan · Pelajaran

Pengoptimuman Ruang untuk DP 2D

Kurangkan ruang LCS dan jarak suntingan daripada O(mn) kepada O(min(m,n)) dengan mengekalkan baris semasa dan sebelumnya sahaja dalam jadual DP.

Pelajaran 4 daripada 413 langkah

Pengoptimuman Ruang untuk DP 2D ialah pelajaran Persediaan Temu Duga Pengaturcaraan percuma di CoddyKit. Ini ialah pelajaran 4 daripada 4. Anda boleh membaca keseluruhan pelajaran di bawah secara percuma — kemudian berlatih secara praktikal dalam pelayar menggunakan penyunting kod terbina dalam dan tutor kecerdasan buatan 24/7. Pelajaran ini merupakan sebahagian daripada laluan pembelajaran Persediaan Temu Duga Pengaturcaraan, dan kemajuan anda disegerakkan merentas web serta aplikasi CoddyKit. Kursus Persediaan Temu Duga Pengaturcaraan merangkumi sejumlah 4 pelajaran.

Kepentingan Ruang dalam DP 2D

Jadual DP 2D untuk rentetan sepanjang 1000 memerlukan 1000×1000 = 1,000,000 sel — kira-kira 8 MB untuk integer 64-bit. Bagi jujukan yang lebih panjang seperti penjajaran DNA dan perbezaan teks besar, keadaan ini menjadi tidak praktikal. Pemerhatian utama ialah kebanyakan rumus rekursens DP 2D hanya melihat baris semasa dan baris sebelumnya, jadi keseluruhan jadual boleh dimampatkan menjadi satu atau dua tatasusunan 1D. Inilah asas pengoptimuman 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')

Corak Tatasusunan Bergilir

Corak tatasusunan bergilir menggantikan jadual 2D penuh dengan tatasusunan 1D yang mewakili baris sebelumnya. Apabila mengira baris i, anda mengemas kini setiap sel j menggunakan nilai semasa dp[j] (yang masih menyimpan dp[i-1][j] daripada baris sebelumnya) dan dp[j-1] yang baru dikemas kini (iaitu dp[i][j-1]). Pemboleh ubah diagonal menangkap dp[i-1][j-1] sebelum nilai itu ditulis ganti. Corak ini boleh digunakan untuk LCS, jarak suntingan dan kebanyakan 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)

Bagi LCS, pastikan text1 ialah rentetan yang lebih pendek supaya n lebih kecil. Peruntukkan tatasusunan 1D bersaiz n+1. Proses baris demi baris. Pada setiap sel: simpan temp = dp[j] (iaitu dp[i-1][j]). Kemudian: jika aksara sepadan, dp[j] = diag + 1; jika tidak, dp[j] = max(dp[j], dp[j-1]). Akhir sekali, tetapkan diag = temp. Selepas semua baris 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')) # 4

Jarak Suntingan dengan Ruang O(n)

Jarak suntingan menggunakan corak tatasusunan bergilir yang sama. Tatasusunan 1D awal mewakili baris 0: dp[j] = j (menyisipkan j aksara). Bagi setiap baris i, tetapkan dp[0] = i (memadamkan i aksara) dan simpan diag = dp[0] sebelum kemas kini. Dalam gelung dalaman, simpan temp = dp[j], kira nilai baharu daripada sisipan (dp[j-1]+1), pemadaman (dp[j]+1) dan penggantian (diag + cost), kemudian 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'))  # 5

Jumlah Laluan Minimum dengan Ruang O(n)

Untuk masalah Jumlah Laluan Minimum pada kisi, tatasusunan gelongsor 1D bermula sebagai jumlah awalan baris pertama (hanya ada satu cara untuk sampai ke setiap sel pada baris pertama). Bagi setiap baris seterusnya, kemas kini dari kiri ke kanan: dp[j] sebelum dikemas kini ialah nilai daripada baris di atas (dp[i-1][j]), manakala dp[j-1] yang baru dikemas kini ialah nilai dari sebelah kiri. Elemen pepenjuru tidak diperlukan di sini kerana jumlah laluan minimum tidak memerlukan sel pepenjuru.

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

Apabila Akses Pepenjuru Diperlukan

Tidak semua masalah DP 2D boleh dimampatkan dengan tatasusunan gelongsor yang mudah kerana sesetengahnya memerlukan elemen pepenjuru dp[i-1][j-1] selepas dp[j] ditulis ganti. Penyelesaiannya sentiasa sama: simpan temp = dp[j] sebelum mengemas kininya, kemudian gunakannya sebagai diag untuk pengiraan lajur seterusnya. Tinjauan satu sel ke hadapan ini mengendalikan semua rumus ulangan tiga arah (LCS, jarak suntingan) dengan kemas.

# 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

Pengoptimuman Ruang Beg Galas 2D

Masalah Beg Galas 0/1 juga mendapat manfaat daripada pengoptimuman ruang. Jadual 2D penuh mempunyai dimensi (bilangan item+1) × (kapasiti+1). Tatasusunan gelongsor mengurangkannya kepada O(kapasiti). Perbezaan penting berbanding LCS/jarak suntingan ialah: lakukan lelaran pada dimensi kapasiti secara songsang (daripada tinggi ke rendah). Ini memastikan setiap item dikira paling banyak sekali — lelaran ke hadapan akan membolehkan sesuatu item dipilih berulang 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)

Lelaran Ke Hadapan berbanding Songsang

Mengetahui arah untuk melakukan lelaran gelung dalam adalah penting: songsang untuk beg galas 0/1 (setiap item digunakan paling banyak sekali — melihat kembali keadaan terdahulu menghalang penggunaan semula). Ke hadapan untuk beg galas tanpa had (setiap item boleh digunakan semula — melihat keadaan yang telah dikemas kini membolehkan penggunaan berbilang kali). Kesilapan ini secara senyap-senyap menukar masalah 0/1 menjadi masalah tanpa had atau sebaliknya. Sentiasa sahkan kekangan sebelum memilih arah.

# 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+... )

Laluan Unik dengan Ruang O(n)

Untuk laluan unik, seluruh jadual boleh digantikan dengan satu baris. Mulakan semua sel dengan 1 (baris pertama). Bagi setiap baris seterusnya, kemas kini dari kiri ke kanan: dp[j] += dp[j-1]. Elemen pepenjuru tidak diperlukan kerana rumus ulangan hanya menggunakan sel di atas (dp[j], nilai semasa sebelum dikemas kini) dan sel di sebelah kiri (dp[j-1], yang telah dikemas kini). Inilah pemampatan 2D→1D yang paling mudah.

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

Penyangga Dua Baris untuk Rumus Ulangan Kompleks

Apabila rumus ulangan memerlukan sel daripada dua atau lebih baris terdahulu (contohnya, sesetengah variasi DP selang atau pengurangan DP 3D), gunakan penyangga dua baris: kekalkan tatasusunan prev dan curr, kemudian saling tukarkannya selepas setiap baris. Ini memberikan ruang O(2n) = O(n). Bagi rumus ulangan yang merujuk kembali kepada k baris, kekalkan k tatasusunan sebagai penimbal bulat. Corak ini meluaskan corak tatasusunan gelongsor 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'))  # 4

Apabila Pengoptimuman Ruang Tidak Boleh Dilakukan

Pengoptimuman ruang tidak sentiasa boleh dilakukan. Jika Anda perlu membina semula penyelesaian optimum (bukan sekadar nilainya), Anda biasanya memerlukan jadual penuh untuk penjejakan balik. Cara mengatasinya termasuk: (1) Menyimpan jadual keputusan berasingan yang mempunyai saiz sama. (2) Menggunakan algoritma Hirschberg, yang mengira LCS dalam masa O(mn) dan ruang O(min(m,n)), termasuk pembinaan semula, dengan membahagikan masalah pada titik tengah secara rekursif. (3) Menerima ruang O(mn) apabila pembinaan semula 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')

Semakan Pantas

Uji pemahaman Anda tentang konsep Struktur Data & Algoritma — Persediaan Temu Duga Pengekodan daripada pelajaran ini.

Rumusan Pelajaran

Dalam pelajaran ini Anda telah mempelajari bahawa: jadual DP 2D boleh dimampatkan kepada ruang O(n) menggunakan tatasusunan 1D gelongsor apabila hanya baris sebelumnya diperlukan, corak pemboleh ubah pepenjuru (simpan temp sebelum menulis ganti) mengendalikan rumus ulangan yang memerlukan nilai pada kedudukan pepenjuru daripada baris dan lajur sebelumnya, dan beg galas 0/1 melakukan lelaran kapasiti secara songsang manakala beg galas tanpa had melakukan lelaran ke hadapan. Seterusnya kita akan mengkaji templat Penjejakan Balik: Pilih, Teroka, Nyahpilih — asas algoritma carian menyeluruh.

Percuma untuk bermula

Pelajari Persediaan Temu Duga Pengaturcaraan dengan tutor kecerdasan buatan — percuma

Tulis dan jalankan kod sebenar dalam pelayar anda, dapatkan bantuan segera daripada tutor kecerdasan buatan yang tersedia 24/7, dan sambung semula dari tempat anda berhenti di web atau dalam aplikasi.

Kursus
90
Pelajaran
360

Soalan Lazim

Adakah pelajaran “Pengoptimuman Ruang untuk DP 2D” percuma?

Ya — teks penuh “Pengoptimuman Ruang untuk DP 2D” boleh dibaca secara percuma di web ini. Untuk berlatih secara interaktif menggunakan penyunting kod terbina dalam dan tutor kecerdasan buatan 24/7, serta membuka kunci baki kursus Persediaan Temu Duga Pengaturcaraan, tingkat taraf kepada CoddyKit PRO. Kursus Persediaan Temu Duga Pengaturcaraan merangkumi sejumlah 4 pelajaran.

Apakah yang akan saya pelajari dalam “Pengoptimuman Ruang untuk DP 2D”?

Kurangkan ruang LCS dan jarak suntingan daripada O(mn) kepada O(min(m,n)) dengan mengekalkan baris semasa dan sebelumnya sahaja dalam jadual DP. Anda berlatih Persediaan Temu Duga Pengaturcaraan menggunakan kod praktikal yang dijalankan terus dalam pelayar, manakala tutor kecerdasan buatan 24/7 menjawab soalan anda semasa anda mengikuti pelajaran.

Adakah saya memerlukan pengalaman untuk memulakan Persediaan Temu Duga Pengaturcaraan?

Tiada pengalaman terdahulu diperlukan. Pembelajaran Persediaan Temu Duga Pengaturcaraan di CoddyKit disusun untuk pelajar daripada peringkat pemula hingga lanjutan, jadi anda boleh bermula di sini atau dari awal dan belajar mengikut kadar anda sendiri. Ini ialah pelajaran 4 daripada 4.

Berapa lamakah pelajaran “Pengoptimuman Ruang untuk DP 2D” diambil?

Kebanyakan pelajaran CoddyKit mengambil masa kira-kira 5–10 minit. Setiap pelajaran ringkas dan interaktif, jadi anda boleh membuat kemajuan secara berterusan dan menyambung tepat dari tempat anda berhenti di web atau aplikasi.

Bolehkah saya menulis dan menjalankan kod dalam pelajaran Persediaan Temu Duga Pengaturcaraan ini?

Ya. Setiap pelajaran Persediaan Temu Duga Pengaturcaraan menyertakan penyunting kod terbina dalam, jadi anda boleh menulis dan menjalankan kod sebenar terus dalam pelayar serta menerima maklum balas kecerdasan buatan serta-merta — tanpa memerlukan persediaan setempat.

Semua pelajaran dalam kursus ini

  1. Laluan Unik dan Jumlah Laluan Minimum pada Grid
  2. Subjujukan Sepunya Terpanjang
  3. Jarak Suntingan (Levenshtein)
  4. Pengoptimuman Ruang untuk DP 2D
← Kembali ke Persediaan Temu Duga Pengaturcaraan