Skip to content
Clases

Edit Distance

Para reducir ambigüedades, en este artículo nos referiremos a la cadena vacía como ϵ\epsilon (epsilon).

Dadas dos cadenas s1s1 y s2s2, el objetivo es encontrar el número mínimo de operaciones para convertir s1s1 en s2s2.

Las operaciones permitidas son:

  1. Insertar un carácter.
  2. Eliminar un carácter.
  3. Reemplazar un carácter.

Este es un problema clásico de optimización que se resuelve elegantemente con Programación Dinámica.


La clave de la DP es resolver un problema grande dividiéndolo en subproblemas más pequeños. En lugar de pensar en los strings completos, nos enfocamos en sus prefijos.

Si queremos calcular dist(s1, s2), consideramos los últimos caracteres que estamos analizando (digamos índices ii y jj).

  • Caso A: Los caracteres son iguales (s1[i]==s2[j]s1[i] == s2[j]) ¡Genial! No necesitamos hacer nada. El costo no aumenta. dp[i][j]=dp[i1][j1]dp[i][j] = dp[i-1][j-1]

  • Caso B: Los caracteres son distintos (s1[i]s2[j]s1[i] \neq s2[j]) Debemos realizar una operación. Tenemos 3 opciones y elegimos la más barata (el mínimo):

    1. Reemplazar: Costo 1+dp[i1][j1]1 + dp[i-1][j-1]
    2. Eliminar: Costo 1+dp[i1][j]1 + dp[i-1][j]
    3. Insertar: Costo 1+dp[i][j1]1 + dp[i][j-1]

dp[i][j]=1+min(Reemplazar,Eliminar,Insertar)dp[i][j] = 1 + \min(Reemplazar, Eliminar, Insertar)


Antes de rellenar la tabla, necesitamos establecer los puntos de partida (convertir algo en una cadena vacía o viceversa).

1. Primera Fila: Convertir ϵ\epsilon en s2s2

Section titled “1. Primera Fila: Convertir ϵ\epsilonϵ en s2s2s2”

Representa convertir un string vacío en un prefijo de s2s2. La única forma es insertando caracteres.

  • dp[0][j] = j (Costo = jj inserciones).

2. Primera Columna: Convertir s1s1 en ϵ\epsilon

Section titled “2. Primera Columna: Convertir s1s1s1 en ϵ\epsilonϵ”

Representa convertir un prefijo de s1s1 en un string vacío. La única forma es eliminando caracteres.

  • dp[i][0] = i (Costo = ii eliminaciones).

Construyamos la tabla.

  • Filas: horse (s1s1)
  • Columnas: ros (s2s2)
ϵ\epsilonros
ϵ\epsilon0123
h1123
o2212
r3222
s4332
e5443

Aquí tienes la solución iterativa (Bottom-Up) que llena la tabla que vimos arriba.

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
int EditDistance(string word1, string word2) {
int n = word1.length();
int m = word2.length();
// dp[i][j] = costo para convertir los primeros 'i' chars de word1
// en los primeros 'j' chars de word2.
vector<vector<int>> dp(n + 1, vector<int>(m + 1));
// 1. Casos base: Llenar bordes
for (int i = 0; i <= n; ++i) dp[i][0] = i; // Eliminaciones
for (int j = 0; j <= m; ++j) dp[0][j] = j; // Inserciones
// 2. Llenar el resto de la tabla
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
// Nota: word1[i-1] es el caracter actual porque los strings son 0-indexados
if (word1[i - 1] == word2[j - 1]) {
// Coincidencia: Tomamos el valor de la diagonal sin sumar costo
dp[i][j] = dp[i - 1][j - 1];
} else {
// Diferencia: 1 + el mínimo de las 3 operaciones posibles
dp[i][j] = 1 + min({
dp[i - 1][j - 1], // Reemplazar (Diagonal)
dp[i - 1][j], // Eliminar (Arriba)
dp[i][j - 1] // Insertar (Izquierda)
});
}
}
}
// La respuesta final está en la esquina inferior derecha
return dp[n][m];
}

Practica estas variaciones para dominar la DP en strings: