Edit Distance
Para reducir ambigüedades, en este artículo nos referiremos a la cadena vacía como (epsilon).
El Problema
Section titled “El Problema”Dadas dos cadenas y , el objetivo es encontrar el número mínimo de operaciones para convertir en .
Las operaciones permitidas son:
- Insertar un carácter.
- Eliminar un carácter.
- Reemplazar un carácter.
Este es un problema clásico de optimización que se resuelve elegantemente con Programación Dinámica.
Subproblemas y Recurrencia
Section titled “Subproblemas y Recurrencia”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 y ).
-
Caso A: Los caracteres son iguales () ¡Genial! No necesitamos hacer nada. El costo no aumenta.
-
Caso B: Los caracteres son distintos () Debemos realizar una operación. Tenemos 3 opciones y elegimos la más barata (el mínimo):
- Reemplazar: Costo
- Eliminar: Costo
- Insertar: Costo
Los Casos Base (Los Bordes)
Section titled “Los Casos Base (Los Bordes)”Antes de rellenar la tabla, necesitamos establecer los puntos de partida (convertir algo en una cadena vacía o viceversa).
1. Primera Fila: Convertir en
Section titled “1. Primera Fila: Convertir ϵ\epsilonϵ en s2s2s2”Representa convertir un string vacío en un prefijo de . La única forma es insertando caracteres.
dp[0][j] = j(Costo = inserciones).
2. Primera Columna: Convertir en
Section titled “2. Primera Columna: Convertir s1s1s1 en ϵ\epsilonϵ”Representa convertir un prefijo de en un string vacío. La única forma es eliminando caracteres.
dp[i][0] = i(Costo = eliminaciones).
Ejemplo: “horse” “ros”
Section titled “Ejemplo: “horse” →\to→ “ros””Construyamos la tabla.
- Filas:
horse() - Columnas:
ros()
| r | o | s | ||
|---|---|---|---|---|
| 0 | 1 | 2 | 3 | |
| h | 1 | 1 | 2 | 3 |
| o | 2 | 2 | 1 | 2 |
| r | 3 | 2 | 2 | 2 |
| s | 4 | 3 | 3 | 2 |
| e | 5 | 4 | 4 | 3 |
Implementación en C++
Section titled “Implementación en C++”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];}Ejercicios Recomendados 🏋️
Section titled “Ejercicios Recomendados 🏋️”Practica estas variaciones para dominar la DP en strings:
-
- El problema clásico. Intenta implementarlo sin mirar el código.
-
712. Minimum ASCII Delete Sum (LeetCode)
- Variación: En lugar de que borrar cueste
1, el costo es el valor ASCII del carácter borrado (ej. borrar ‘a’ cuesta 97). - Pista: Cambia los
+ 1en la fórmula por+ word1[i].
- Variación: En lugar de que borrar cueste
-
1143. Longest Common Subsequence (LeetCode)
- Este es el “primo hermano” del Edit Distance. La lógica es casi idéntica, pero solo permitiendo “coincidencias” y buscando maximizar en lugar de minimizar costo.