Persediaan Temu Duga Pengaturcaraan · Pelajaran

Jarak Suntingan (Levenshtein)

Terbitkan pengulangan jarak suntingan bagi operasi sisipan/pemadaman/penggantian dan isikan jadual DP untuk pasangan rentetan dengan panjang berbeza.

Pelajaran 3 daripada 413 langkah

Jarak Suntingan (Levenshtein) ialah pelajaran Persediaan Temu Duga Pengaturcaraan percuma di CoddyKit. Ini ialah pelajaran 3 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.

Masalah Jarak Suntingan

Jarak Suntingan (jarak Levenshtein, LeetCode 72) menanyakan: apakah bilangan minimum operasi sisipan, pemadaman atau penggantian yang diperlukan untuk mengubah satu rentetan menjadi rentetan yang lain? Sebagai contoh, untuk mengubah 'horse' menjadi 'ros': gantikan 'h'→'r' (horse→rorse), padamkan 'r' (rorse→rose), dan padamkan 'e' (rose→ros) — 3 operasi. Jarak suntingan menjadi asas penting dalam penyemak ejaan, penjajaran DNA dan pemadanan kabur.

# Allowed operations:
# Insert: 'abc' → 'abXc' (insert X)
# Delete: 'abc' → 'ac' (delete b)
# Replace: 'abc' → 'aXc' (replace b with X)

# horse → ros: 3 operations
# 1. horse → rorse (replace h with r)
# 2. rorse → rose  (delete r at index 1)
# 3. rose  → ros   (delete e)
print('Edit distance horse→ros: 3')
print('Edit distance intention→execution: 5')

Keadaan DP dan Rumus Rekurens

Takrifkan dp[i][j] sebagai jarak suntingan minimum antara word1[:i] dan word2[:j]. Jika word1[i-1] == word2[j-1], tiada operasi diperlukan: dp[i][j] = dp[i-1][j-1]. Jika tidak, ambil nilai minimum daripada tiga operasi: sisip dp[i][j-1] + 1, padam dp[i-1][j] + 1, dan ganti dp[i-1][j-1] + 1. Kes asas: dp[i][0] = i (padam semua daripada word1) dan dp[0][j] = j (sisip semua daripada word2).

def edit_distance(word1, word2):
    m, n = len(word1), len(word2)
    dp = [[0]*(n+1) for _ in range(m+1)]
    # Base cases
    for i in range(m+1): dp[i][0] = i  # delete all of word1
    for j in range(n+1): dp[0][j] = j  # insert all of word2
    for i in range(1, m+1):
        for j in range(1, n+1):
            if word1[i-1] == word2[j-1]:
                dp[i][j] = dp[i-1][j-1]  # no cost
            else:
                dp[i][j] = 1 + min(
                    dp[i][j-1],    # insert
                    dp[i-1][j],    # delete
                    dp[i-1][j-1]   # replace
                )
    return dp[m][n]

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

Memahami Tiga Operasi

Ketiga-tiga operasi dipetakan secara langsung kepada pergerakan dalam jadual DP: Ganti dp[i-1][j-1]+1 — kita memadankan kedua-dua aksara tetapi membayar kos satu. Padam daripada word1 dp[i-1][j]+1 — buang satu aksara daripada word1 (bergerak ke atas dalam jadual). Sisip ke dalam word1 dp[i][j-1]+1 — sisip satu aksara untuk memadankan word2 (bergerak ke kiri). Nilai minimum daripada ketiga-tiganya menghasilkan laluan suntingan yang optimum.

# Visualise the DP table for 'cat' → 'cut'
# dp[i][j] = min edits for word1[:i] vs word2[:j]

word1, word2 = 'cat', 'cut'
m, n = len(word1), len(word2)
dp = [[0]*(n+1) for _ in range(m+1)]
for i in range(m+1): dp[i][0] = i
for j in range(n+1): dp[0][j] = j
for i in range(1, m+1):
    for j in range(1, n+1):
        if word1[i-1]==word2[j-1]: dp[i][j]=dp[i-1][j-1]
        else: dp[i][j]=1+min(dp[i][j-1],dp[i-1][j],dp[i-1][j-1])
print('  ', ' '.join(' '+word2))
for i, row in enumerate(dp):
    print((' ' if i==0 else word1[i-1]), row)

Pengoptimuman Ruang kepada O(n)

Jarak suntingan hanya memerlukan baris semasa dan baris sebelumnya. Gunakan tatasusunan 1D bersaiz n+1 dan jejak nilai diagonal (dp[i-1][j-1]) secara berasingan sebelum setiap kemas kini sel. Proses dari kiri ke kanan: temp = dp[j] (nilai lama = dp[i-1][j]), kemudian kemas kini dp[j] menggunakan dp[j] (pemadaman), dp[j-1] (sisipan) dan diagonal (penggantian).

def edit_distance_1d(word1, word2):
    m, n = len(word1), len(word2)
    dp = list(range(n + 1))  # initial row: 0,1,2,...,n
    for i in range(1, m + 1):
        diag = dp[0]       # dp[i-1][0]
        dp[0] = i          # dp[i][0] = i
        for j in range(1, n + 1):
            temp = dp[j]   # dp[i-1][j] before overwrite
            if word1[i-1] == word2[j-1]:
                dp[j] = diag
            else:
                dp[j] = 1 + min(dp[j],     # delete
                                dp[j-1],   # insert
                                diag)      # replace
            diag = temp
    return dp[n]

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

Membina Semula Operasi Suntingan

Untuk membina semula jujukan suntingan sebenar, jejak balik jadual DP bermula dari (m, n). Pada setiap sel: jika word1[i-1] == word2[j-1], bergerak secara menyerong (tiada operasi). Jika tidak, tentukan jiran yang manakah daripada tiga jiran memberikan nilai minimum dan catat operasi yang sepadan. Proses ini menghasilkan skrip suntingan secara terbalik; balikkan skrip itu untuk mendapatkan jawapan akhir.

def edit_ops(word1, word2):
    m, n = len(word1), len(word2)
    dp = [[0]*(n+1) for _ in range(m+1)]
    for i in range(m+1): dp[i][0]=i
    for j in range(n+1): dp[0][j]=j
    for i in range(1,m+1):
        for j in range(1,n+1):
            if word1[i-1]==word2[j-1]: dp[i][j]=dp[i-1][j-1]
            else: dp[i][j]=1+min(dp[i][j-1],dp[i-1][j],dp[i-1][j-1])
    ops, i, j = [], m, n
    while i>0 or j>0:
        if i>0 and j>0 and word1[i-1]==word2[j-1]:
            i-=1; j-=1
        elif j>0 and (i==0 or dp[i][j-1]<=dp[i-1][j] and dp[i][j-1]<=dp[i-1][j-1]):
            ops.append(f'Insert {word2[j-1]} at pos {i}'); j-=1
        elif i>0 and (j==0 or dp[i-1][j]<=dp[i][j-1] and dp[i-1][j]<=dp[i-1][j-1]):
            ops.append(f'Delete {word1[i-1]} at pos {i-1}'); i-=1
        else:
            ops.append(f'Replace {word1[i-1]} with {word2[j-1]}'); i-=1; j-=1
    return list(reversed(ops))

for op in edit_ops('horse', 'ros'): print(op)

Semakan Jarak Satu Suntingan

Masalah temu duga yang lebih mudah: adakah dua rentetan berbeza dengan tepat satu suntingan? Ini boleh dilakukan dalam O(n) tanpa DP. Telusuri kedua-dua rentetan secara serentak. Apabila berlaku ketidakpadanan, cuba ketiga-tiga operasi (langkau satu aksara dalam s1, langkau satu aksara dalam s2, langkau kedua-duanya) dan semak sama ada bahagian yang berbaki adalah sama. Jika berlaku dua ketidakpadanan, pulangkan nilai palsu. Pendekatan tamak ini mengelakkan DP penuh O(mn) apabila anda hanya perlu menentukan sama ada jaraknya ≤ 1.

def is_one_edit_distance(s, t):
    m, n = len(s), len(t)
    if abs(m - n) > 1: return False
    if m > n: return is_one_edit_distance(t, s)  # ensure m <= n
    for i in range(m):
        if s[i] != t[i]:
            if m == n:
                return s[i+1:] == t[i+1:]   # replace
            else:
                return s[i:] == t[i+1:]     # insert into s (delete from t)
    return m + 1 == n  # all matched, lengths differ by 1

print(is_one_edit_distance('ab', 'acb'))   # True (insert c)
print(is_one_edit_distance('ab', 'ab'))    # False (zero edits)
print(is_one_edit_distance('ab', 'abc'))   # True (append c)
print(is_one_edit_distance('ab', 'xyz'))   # False

Perbandingan Jarak Suntingan dengan LCS

Jarak suntingan (dengan ketiga-tiga operasi) dan LCS memberikan dua sudut pandang yang saling melengkapi tentang keserupaan rentetan. Jarak suntingan mengira perbezaan; LCS mengira kesamaan. Apabila hanya sisipan dan pemadaman dibenarkan (tanpa penggantian), jarak suntingan = m + n - 2×LCS. Apabila penggantian dibenarkan, DP berbeza sedikit: nilai pepenjuru ialah dp[i-1][j-1] apabila sepadan (tanpa kos), atau dp[i-1][j-1]+1 apabila berlaku penggantian. Kedua-dua algoritma berjalan dalam masa O(mn).

def lcs_len(s1, s2):
    m, n = len(s1), len(s2)
    dp = [[0]*(n+1) for _ in range(m+1)]
    for i in range(1,m+1):
        for j in range(1,n+1):
            if s1[i-1]==s2[j-1]: dp[i][j]=dp[i-1][j-1]+1
            else: dp[i][j]=max(dp[i-1][j],dp[i][j-1])
    return dp[m][n]

def edit_insert_delete_only(s1, s2):
    return len(s1) + len(s2) - 2 * lcs_len(s1, s2)

print(edit_insert_delete_only('sea', 'eat'))  # 2
print(edit_distance('sea', 'eat'))            # 2 (same here: replace not needed)

Pemadanan Rentetan Kabur

Jarak suntingan menjadi asas kepada pemadanan kabur dalam dunia sebenar. Penyemak ejaan mencadangkan pembetulan dalam jarak suntingan 1 atau 2 daripada perkataan yang ditaip. Cabaran pada skala besar ialah mengelakkan perbandingan O(mn × saiz kamus). Penyelesaiannya termasuk pepohon BK (pepohon metrik untuk jarak suntingan), pengindeksan n-gram dan algoritma pemadanan rentetan anggaran seperti Bitap. Memahami DP yang mendasarinya membantu anda menilai kecekapan alat peringkat lebih tinggi ini.

def spell_suggest(typed, dictionary, max_dist=2):
    '''Return words in dictionary within max_dist edits of typed.'''
    suggestions = []
    for word in dictionary:
        if abs(len(typed) - len(word)) <= max_dist:
            if edit_distance(typed, word) <= max_dist:
                suggestions.append(word)
    return suggestions

def edit_distance(w1, w2):
    dp = list(range(len(w2)+1))
    for i,c1 in enumerate(w1,1):
        prev = i
        for j,c2 in enumerate(w2,1):
            temp = dp[j]
            dp[j] = prev if c1==c2 else 1+min(dp[j],prev,dp[j-1])
            prev = temp
    return dp[len(w2)]

dictionary = ['horse', 'worse', 'house', 'morse', 'nurse']
print(spell_suggest('harse', dictionary))  # horse, worse, house, morse

Jarak Suntingan Berwajaran

Dalam sesetengah aplikasi, operasi yang berbeza mempunyai kos yang berbeza. Sebagai contoh, menukar susunan aksara bersebelahan (kesilapan taip yang lazim) mungkin berharga lebih rendah daripada penggantian penuh. Jarak Damerau-Levenshtein menambahkan penukaran susunan sebagai operasi keempat. DP diperluas dengan turut menyemak dp[i-2][j-2]+1 apabila word1[i-1]==word2[j-2] dan word1[i-2]==word2[j-1]. Hal ini memodelkan kesilapan taip pada papan kekunci dengan lebih tepat.

def damerau_levenshtein(s, t):
    m, n = len(s), len(t)
    dp = [[0]*(n+1) for _ in range(m+1)]
    for i in range(m+1): dp[i][0]=i
    for j in range(n+1): dp[0][j]=j
    for i in range(1,m+1):
        for j in range(1,n+1):
            cost = 0 if s[i-1]==t[j-1] else 1
            dp[i][j] = min(
                dp[i-1][j]+1,     # delete
                dp[i][j-1]+1,     # insert
                dp[i-1][j-1]+cost # replace
            )
            # Transposition
            if i>1 and j>1 and s[i-1]==t[j-2] and s[i-2]==t[j-1]:
                dp[i][j] = min(dp[i][j], dp[i-2][j-2]+1)
    return dp[m][n]

print(damerau_levenshtein('CA', 'ABC'))   # 2
print(damerau_levenshtein('ab', 'ba'))    # 1 (transposition)

Penjajaran Jujukan DNA

Bioinformatik menggunakan varian jarak suntingan untuk penjajaran jujukan DNA. Algoritma Needleman-Wunsch ialah DP penjajaran global yang berkait rapat dengan LCS dan jarak suntingan; padanan memberikan +1, ketidakpadanan memberikan -1, manakala jurang (sisipan/pemadaman) memberikan penalti. Varian Smith-Waterman melakukan penjajaran setempat (mencari subrentetan yang paling sepadan). Kedua-duanya ialah algoritma DP O(mn) dengan struktur pengisian jadual yang sama.

def needleman_wunsch(seq1, seq2, match=1, mismatch=-1, gap=-1):
    m, n = len(seq1), len(seq2)
    dp = [[0]*(n+1) for _ in range(m+1)]
    for i in range(m+1): dp[i][0] = i * gap
    for j in range(n+1): dp[0][j] = j * gap
    for i in range(1,m+1):
        for j in range(1,n+1):
            score = match if seq1[i-1]==seq2[j-1] else mismatch
            dp[i][j] = max(
                dp[i-1][j-1] + score,  # align
                dp[i-1][j] + gap,      # gap in seq2
                dp[i][j-1] + gap       # gap in seq1
            )
    return dp[m][n]

print(needleman_wunsch('GATTACA', 'GCATGCU'))  # alignment score

Pendekatan Temu Duga untuk Jarak Suntingan

Apabila ditanya tentang jarak suntingan dalam temu duga: (1) Sahkan operasi yang dibenarkan (sisip/padam/ganti). (2) Takrifkan keadaan DP dengan jelas. (3) Tulis ketiga-tiga kes dan rumus rekursens secara eksplisit. (4) Nyatakan kes asas: dp[i][0]=i dan dp[0][j]=j. (5) Sebut pengoptimuman ruang O(n). (6) Jika masa mengizinkan, telusuri contoh kecil seperti 'cat'→'cut' (1 penggantian) untuk mengesahkannya. Masa O(mn) serta ruang O(mn) → O(n) ialah batas kerumitan standard.

# Clean interview solution
def min_distance(word1, word2):
    m, n = len(word1), len(word2)
    # O(n) space with rolling row
    dp = list(range(n + 1))
    for i in range(1, m + 1):
        diag = dp[0]   # dp[i-1][0]
        dp[0] = i
        for j in range(1, n + 1):
            temp = dp[j]
            if word1[i-1] == word2[j-1]:
                dp[j] = diag
            else:
                dp[j] = 1 + min(dp[j], dp[j-1], diag)
            diag = temp
    return dp[n]

# Time: O(mn), Space: O(n)
print(min_distance('horse', 'ros'))          # 3
print(min_distance('intention', 'execution')) # 5
print(min_distance('', 'abc'))               # 3
print(min_distance('abc', ''))               # 3

Semakan Pantas

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

Rumusan Pelajaran

Dalam pelajaran ini, anda mempelajari: jarak suntingan dp[i][j] = min(dp[i][j-1]+1, dp[i-1][j]+1, dp[i-1][j-1]+cost) dengan kos=0 apabila sepadan, selainnya 1, kes asas dp[i][0]=i dan dp[0][j]=j mewakili penukaran kepada atau daripada rentetan kosong, serta pengoptimuman ruang O(n) menggunakan tatasusunan 1D bergilir dengan pemboleh ubah pepenjuru. Seterusnya, kita menggunakan teknik tatasusunan bergilir yang sama untuk mengurangkan ruang jadual DP 2D daripada O(mn) kepada O(min(m,n)).

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 “Jarak Suntingan (Levenshtein)” percuma?

Ya — teks penuh “Jarak Suntingan (Levenshtein)” 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 “Jarak Suntingan (Levenshtein)”?

Terbitkan pengulangan jarak suntingan bagi operasi sisipan/pemadaman/penggantian dan isikan jadual DP untuk pasangan rentetan dengan panjang berbeza. 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 3 daripada 4.

Berapa lamakah pelajaran “Jarak Suntingan (Levenshtein)” 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