0Pricing
Coding Interview Prep · Lektion

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')) # 5

Die 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')) # 5

Rekonstruktion 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'))   # False

Vergleich 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, morse

Gewichtete 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 score

Vorgehensweise 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', ''))               # 3

Schnelltest

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

  1. Eindeutige Pfade und minimale Pfadsumme in Rastern
  2. Längste gemeinsame Teilsequenz
  3. Editierdistanz (Levenshtein)
  4. Speicheroptimierung für 2D-DP
← Zurück zu Coding Interview Prep