Editierdistanz (Levenshtein)
Leiten Sie die Rekurrenz der Editierdistanz für Einfüge-, Lösch- und Ersetzoperationen her und füllen Sie die DP-Tabelle für Stringpaare unterschiedlicher Länge.
Editierdistanz (Levenshtein) ist eine kostenlose Coding Interview Prep-Lektion auf CoddyKit. Dies ist Lektion 3 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des Coding Interview Prep-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der Coding Interview Prep-Kurs umfasst insgesamt 4 Lektionen.
Das Problem der Edit-Distanz
Die Edit-Distanz (Levenshtein-Distanz, LeetCode 72) fragt: Wie viele Einfüge-, Lösch- oder Ersetzungsoperationen sind mindestens erforderlich, um eine Zeichenkette in eine andere umzuwandeln? Um beispielsweise 'horse' in 'ros' umzuwandeln, ersetzen Sie 'h'→'r' (horse→rorse), löschen Sie 'r' (rorse→rose) und löschen Sie 'e' (rose→ros) – insgesamt 3 Operationen. Die Edit-Distanz ist grundlegend für Rechtschreibprüfungen, DNA-Alignment und unscharfen Abgleich.
# 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')DP-Zustand und Rekurrenz
Definieren Sie dp[i][j] als die minimale Edit-Distanz zwischen word1[:i] und word2[:j]. Wenn word1[i-1] == word2[j-1] gilt, ist keine Operation erforderlich: dp[i][j] = dp[i-1][j-1]. Andernfalls wählen Sie das Minimum aus drei Operationen: Einfügen dp[i][j-1] + 1, Löschen dp[i-1][j] + 1, Ersetzen dp[i-1][j-1] + 1. Basisfälle: dp[i][0] = i (alle Zeichen von word1 löschen) und dp[0][j] = j (alle Zeichen von word2 einfügen).
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')) # 5Die drei Operationen verstehen
Die drei Operationen entsprechen direkt Bewegungen in der DP-Tabelle: Ersetzen dp[i-1][j-1]+1 – beide Zeichen wurden abgeglichen, aber dafür wurde ein Kostenpunkt berechnet. Aus word1 löschen dp[i-1][j]+1 – ein Zeichen aus word1 entfernen (in der Tabelle nach oben gehen). In word1 einfügen dp[i][j-1]+1 – ein Zeichen einfügen, um es an word2 anzugleichen (nach links gehen). Das Minimum der drei Werte liefert den optimalen Bearbeitungspfad.
# 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)Optimierung des Speicherbedarfs auf O(n)
Für die Edit-Distanz werden nur die aktuelle und die vorherige Zeile benötigt. Verwenden Sie ein 1D-Array der Größe n+1 und verfolgen Sie den Wert diagonal (dp[i-1][j-1]) vor jeder Aktualisierung einer Zelle separat. Verarbeiten Sie die Werte von links nach rechts: temp = dp[j] (alter Wert = dp[i-1][j]), und aktualisieren Sie dp[j] mithilfe von dp[j] (Löschen), dp[j-1] (Einfügen) und diagonal (Ersetzen).
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')) # 5Rekonstruktion der Edit-Operationen
Um die konkrete Folge von Bearbeitungen zu rekonstruieren, verfolgen Sie die DP-Tabelle von (m, n) aus rückwärts. Gehen Sie an jeder Zelle wie folgt vor: Wenn word1[i-1] == word2[j-1] gilt, gehen Sie diagonal (keine Operation). Andernfalls ermitteln Sie, welcher der drei Nachbarn den kleinsten Wert geliefert hat, und speichern Sie die entsprechende Operation. Dadurch entsteht die Änderungsfolge in umgekehrter Reihenfolge. Kehren Sie sie für das endgültige Ergebnis um.
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)Prüfung auf eine Edit-Distanz von eins
Ein einfacheres Problem aus Vorstellungsgesprächen lautet: Sind zwei Zeichenketten genau eine Bearbeitung voneinander entfernt? Dies lässt sich ohne DP in O(n) lösen. Durchlaufen Sie beide Zeichenketten gleichzeitig. Bei einer Nichtübereinstimmung probieren Sie alle drei Operationen aus (ein Zeichen in s1 überspringen, ein Zeichen in s2 überspringen, beide Zeichen überspringen) und prüfen Sie, ob die verbleibenden Teilzeichenketten identisch sind. Wenn zwei Nichtübereinstimmungen auftreten, geben Sie False zurück. Dieser Greedy-Ansatz vermeidet die vollständige O(mn)-DP, wenn Sie nur feststellen müssen, ob die Distanz ≤ 1 ist.
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')) # FalseVergleich von Edit-Distanz und LCS
Edit-Distanz (mit allen drei Operationen) und LCS bieten ergänzende Sichtweisen auf die Ähnlichkeit von Zeichenketten. Die Edit-Distanz zählt die Unterschiede, die LCS zählt die Ähnlichkeit. Wenn nur Einfüge- und Löschoperationen erlaubt sind (kein Ersetzen), gilt Edit-Distanz = m + n - 2×LCS. Wenn Ersetzungen erlaubt sind, ist die DP geringfügig anders: Die Diagonale liefert bei einer Übereinstimmung dp[i-1][j-1] (kostenlos) oder bei einer Ersetzung dp[i-1][j-1]+1. Beide Algorithmen benötigen O(mn) Zeit.
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)Unscharfer Zeichenkettenabgleich
Die Edit-Distanz ermöglicht einen realen unscharfen Abgleich. Eine Rechtschreibprüfung schlägt Korrekturen vor, deren Edit-Distanz zum eingegebenen Wort 1 oder 2 beträgt. Die Herausforderung bei großen Datenmengen besteht darin, O(mn × dict_size)-Vergleiche zu vermeiden. Zu den Lösungen gehören BK-Bäume (ein metrischer Baum für die Edit-Distanz), n-Gramm-Indizierung und Algorithmen für den Näherungsabgleich wie Bitap. Wenn Sie die zugrunde liegende DP verstehen, können Sie die Effizienz dieser übergeordneten Werkzeuge besser einschätzen.
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, morseGewichtete Edit-Distanz
In manchen Anwendungen haben verschiedene Operationen unterschiedliche Kosten. Das Vertauschen benachbarter Zeichen (ein häufiger Tippfehler) kann beispielsweise weniger kosten als eine vollständige Ersetzung. Die Damerau-Levenshtein-Distanz fügt die Vertauschung als vierte Operation hinzu. Die DP wird erweitert: Prüfen Sie zusätzlich dp[i-2][j-2]+1, wenn word1[i-1]==word2[j-2] und word1[i-2]==word2[j-1] gilt. Dadurch werden Tastaturtippfehler genauer modelliert.
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)DNA-Sequenzalignment
Die Bioinformatik verwendet Varianten der Edit-Distanz für das DNA-Sequenzalignment. Der Needleman-Wunsch-Algorithmus ist eine DP für globale Alignments, die eng mit LCS und Edit-Distanz verwandt ist. Dabei ergibt eine Übereinstimmung +1, eine Nichtübereinstimmung -1 und eine Lücke (Einfügen/Löschen) eine Strafe. Die Variante Smith-Waterman führt ein lokales Alignment durch (sie findet die am besten übereinstimmende Teilzeichenkette). Beide sind DP-Algorithmen mit O(mn) und derselben Struktur zum Ausfüllen der Tabelle.
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 scoreVorgehensweise bei der Edit-Distanz im Vorstellungsgespräch
Wenn Sie in einem Vorstellungsgespräch nach der Edit-Distanz gefragt werden: (1) Bestätigen Sie die erlaubten Operationen (Einfügen/Löschen/Ersetzen). (2) Definieren Sie den DP-Zustand klar. (3) Schreiben Sie die drei Fälle und die Rekurrenz ausdrücklich auf. (4) Nennen Sie die Basisfälle: dp[i][0]=i und dp[0][j]=j. (5) Erwähnen Sie die Optimierung des Speicherbedarfs auf O(n). (6) Wenn es die Zeit erlaubt, gehen Sie ein kleines Beispiel wie 'cat'→'cut' (1 Ersetzung) durch, um das Ergebnis zu überprüfen. Die Standardgrenzen für die Komplexität sind O(mn) Zeit und O(mn) → O(n) Speicher.
# 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', '')) # 3Schnelltest
Testen Sie Ihr Verständnis der Konzepte aus Data Structures & Algorithms — Coding Interview Prep, die in dieser Lektion behandelt werden.
Zusammenfassung der Lektion
In dieser Lektion haben Sie gelernt: edit distance dp[i][j] = min(dp[i][j-1]+1, dp[i-1][j]+1, dp[i-1][j-1]+cost) mit cost=0 bei Übereinstimmung, andernfalls 1, die Basisfälle dp[i][0]=i und dp[0][j]=j stehen für die Umwandlung in eine leere Zeichenkette bzw. aus einer leeren Zeichenkette, und die Optimierung des Speicherbedarfs auf O(n) ein rollierendes 1D-Array mit einer Diagonalvariablen verwendet. Als Nächstes wenden Sie denselben Trick mit rollierenden Arrays an, um 2D-DP-Tabellen vom Speicherbedarf O(mn) auf O(min(m,n)) zu reduzieren.
Häufig gestellte Fragen
Ist die Lektion „Editierdistanz (Levenshtein)“ kostenlos?
Ja — der vollständige Text von „Editierdistanz (Levenshtein)“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des Coding Interview Prep-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der Coding Interview Prep-Kurs umfasst insgesamt 4 Lektionen.
Was lerne ich in „Editierdistanz (Levenshtein)“?
Leiten Sie die Rekurrenz der Editierdistanz für Einfüge-, Lösch- und Ersetzoperationen her und füllen Sie die DP-Tabelle für Stringpaare unterschiedlicher Länge. Du übst Coding Interview Prep mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.
Brauche ich Erfahrung, um Coding Interview Prep zu starten?
Keine Vorkenntnisse erforderlich. Coding Interview Prep auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 3 von 4.
Wie lange dauert die Lektion „Editierdistanz (Levenshtein)“?
Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.
Kann ich in dieser Coding Interview Prep-Lektion Code schreiben und ausführen?
Ja. Jede Coding Interview Prep-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.
Alle Lektionen in diesem Kurs
- Eindeutige Pfade und minimale Pfadsumme in Rastern
- Längste gemeinsame Teilsequenz
- Editierdistanz (Levenshtein)
- Speicheroptimierung für 2D-DP