El estado debe guardar solo la información necesaria. Si faltan parámetros, la transición queda ambigua; si sobran, la tabla se infla.
Programación Dinámica — Parte 1
Fundamentos de Programación Dinámica: requisitos, estructuras, modelado recursivo, traducción a PD, Factorial y Fibonacci.
Programación Dinámica: idea central
Programación Dinámica es una técnica de diseño algorítmico que toma un problema definido recursivamente, detecta que algunos subproblemas se repiten y evita resolverlos más de una vez guardando sus respuestas.
La recursión ingenua pregunta lo mismo muchas veces. Programación Dinámica responde una vez, guarda la respuesta y reutiliza ese resultado cuando vuelva a necesitarlo.
El intercambio principal es claro: PD suele reducir tiempo, pero aumenta espacio. Ese espacio aparece en tablas, diccionarios, matrices o vectores que almacenan resultados parciales.
Programación Dinámica vs Divide y Vencerás
| Criterio | Divide y Vencerás | Programación Dinámica |
|---|---|---|
| Subproblemas | Normalmente independientes. | Se repiten o se solapan. |
| Recálculo | No suele ser el problema principal. | Es exactamente lo que se quiere eliminar. |
| Estructura | Árbol de llamadas sin reutilización necesaria. | DAG de estados reutilizables. |
| Implementación típica | Recursión directa o división explícita. | Memoización o tabulación. |
| Ejemplo | Merge Sort. | Fibonacci con memoización, LCS, mochila 0/1. |
Si los subproblemas no se repiten, guardar resultados no aporta mucho. PD no es sinónimo de recursión optimizada; es recursión o tabulación con reutilización real de subproblemas.
Los cuatro requisitos de la Programación Dinámica
Antes de diseñar una solución con Programación Dinámica, hay que comprobar que el problema realmente tiene estructura compatible. Si no cumple estos requisitos, usar PD será una decoración costosa, no una técnica correcta.
Los cuatro requisitos de Programación Dinámica
| Requisito | Significado | Pregunta diagnóstica | Si falla |
|---|---|---|---|
| Subestructura óptima | El problema grande puede construirse desde subproblemas del mismo tipo. | ¿La solución de tamaño n depende de soluciones de tamaños menores o estados más pequeños? | No hay una descomposición natural para PD. |
| Principio de optimalidad | La solución óptima global contiene soluciones óptimas de subproblemas. | ¿Puedo tomar decisiones óptimas locales de subproblemas y combinarlas sin romper el óptimo global? | El problema puede requerir búsqueda, backtracking o branch and bound. |
| Subproblemas superpuestos | Los mismos subproblemas aparecen varias veces. | ¿La recursión vuelve a calcular estados idénticos? | La memoización no aporta valor sustancial. |
| Modelo recursivo formulable | La solución puede escribirse con caso base y caso general. | ¿Puedo expresar el valor de un estado en términos de estados más pequeños? | No hay base formal para construir la tabla. |
- Si hay subestructura óptima y subproblemas superpuestos, probablemente hay PD.
- Si hay subestructura óptima pero no hay solapamiento, probablemente es Divide y Vencerás.
- Si hay decisiones, restricciones y poda, probablemente estás más cerca de Backtracking o Branch and Bound.
- Si hay selección local sin reconsideración, probablemente estás más cerca de un algoritmo voraz.
Las tres estructuras de datos de PD
Una solución de Programación Dinámica no es solo una fórmula. En problemas de optimización, normalmente se necesitan estructuras para guardar valores, decisiones y reconstruir la solución final.
Estructuras de datos de Programación Dinámica
| Estructura | Qué almacena | Cuándo se llena | Ejemplo |
|---|---|---|---|
| Tabla de óptimos | El mejor valor de cada subproblema. | A medida que se resuelven los estados. | guarda la longitud de la LCS. |
| Tabla de caminos | La decisión que llevó al óptimo. | Junto con la tabla de óptimos. | Guardar si se vino de arriba, izquierda o diagonal. |
| Vector SOA | La secuencia final de decisiones. | Al final, recorriendo hacia atrás. | La subsecuencia reconstruida en LCS o los objetos elegidos en mochila. |
La respuesta final suele estar en la última celda, pero esa celda solo dice el valor óptimo. Para saber qué decisiones produjeron ese valor, se sigue la tabla de caminos desde el final hasta los casos base.
Si solo necesitas el valor final, puede bastar la tabla de óptimos. Si necesitas reconstruir la solución, necesitas tabla de caminos o alguna forma equivalente de rastrear decisiones.
Primero se modela recursivamente
El error típico es empezar por la tabla. Eso está al revés. En Programación Dinámica primero se modela el problema recursivamente; después, con base en ese modelo, se diseña la tabla o la memoización.
Sin modelo recursivo no hay PD defendible. Una tabla sin estado, transición y caso base es solo almacenamiento accidental.
Componentes del modelo recursivo
| Componente | Pregunta | Resultado esperado |
|---|---|---|
| Estado | ¿Qué información mínima identifica un subproblema? | , , |
| Caso base | ¿Qué estado se resuelve sin subproblemas? | , , |
| Caso general | ¿Cómo se expresa el estado usando estados más pequeños? | Una recurrencia o transición. |
| Decisión | ¿Hay que escoger entre alternativas? | , , tomar/no tomar, avanzar/ignorar. |
| Respuesta final | ¿Qué estado contiene la solución del problema original? | , , |
Formato mínimo del modelo recursivo
Los casos base son las celdas iniciales de la tabla o los retornos directos de la memoización.
La transición debe usar estados más pequeños o previamente calculables.
Este paso evita llenar tablas sin saber cuál celda se debe leer al final.
Del modelo recursivo al modelo de PD
Una vez existe el modelo recursivo, la Programación Dinámica consiste en convertir ese modelo en una estructura de almacenamiento y un orden de cálculo.
Traducción de modelo recursivo a modelo de PD
| En el modelo recursivo | En el modelo de PD | Pregunta de control |
|---|---|---|
| Estado | Celda | ¿Qué dimensiones necesita la tabla? |
| Caso base | Inicialización de filas, columnas o posiciones base. | ¿Qué celdas ya se conocen antes del ciclo? |
| Caso general | Fórmula de llenado. | ¿Qué celdas anteriores necesito para calcular esta? |
| Decisión óptima | Comparación o y registro en tabla de caminos. | ¿Debo reconstruir la solución o solo devolver el valor? |
| Respuesta final | Última celda, celda objetivo o mejor valor global. | ¿Dónde queda la respuesta después de llenar la tabla? |
Flujo correcto: de recursión a PD
Cuándo elegir Top-Down o Bottom-Up
| Criterio | Top-Down | Bottom-Up |
|---|---|---|
| Cercanía al modelo matemático | Muy alta: casi copia la recursión. | Media: exige definir orden iterativo. |
| Subproblemas realmente visitados | Solo calcula los que necesita. | Puede llenar estados que quizá no se usen. |
| Control de memoria | Usa memo más pila recursiva. | Permite optimización espacial más clara. |
| Riesgo | Stack overflow si la profundidad es grande. | Orden incorrecto de llenado si no se respetan dependencias. |
Ejemplo guía: Factorial — del recursivo al PD
Factorial es un ejemplo guía útil porque su modelo recursivo es simple. Pero hay que decir la verdad: no es un gran caso de PD, porque no tiene subproblemas repetidos. Sirve para entender la mecánica de pasar de recursión a tabla, no para presumir una mejora algorítmica.
Paso 1 — Modelo recursivo del factorial
El estado solo necesita un parámetro: .
Por definición, .
También puede declararse como base para simplificar el código.
El problema de tamaño depende de un único subproblema de tamaño .
El estado original ya contiene la respuesta.
factorial(n) BEGIN
IF (n = 0 OR n = 1) THEN BEGIN
RETURN 1;
END
RETURN n * factorial(n - 1);
ENDPaso 2 — Traducción al modelo de PD
Cada posición almacena el factorial de un valor intermedio.
Los casos base del modelo recursivo se convierten en celdas iniciales.
El caso general se convierte en una fórmula iterativa.
Se llena de menor a mayor porque depende de .
La última posición contiene el factorial pedido.
factorialTD(n, Tabla[n]) BEGIN
IF (n = 0 OR n = 1) THEN BEGIN
RETURN 1;
END
IF (Tabla[n] != -1) THEN BEGIN
RETURN Tabla[n];
END
Tabla[n] <- n * factorialTD(n - 1, Tabla);
RETURN Tabla[n];
ENDfactorialBU(n) BEGIN
Tabla[0] <- 1;
Tabla[1] <- 1;
FOR i <- 2 TO n DO BEGIN
Tabla[i] <- Tabla[i - 1] * i;
END
RETURN Tabla[n];
ENDSeguimiento Bottom-Up para n = 5
| i | Fórmula | Valor |
|---|---|---|
| 0 | Caso base | 1 |
| 1 | Caso base | 1 |
| 2 | 2 | |
| 3 | 6 | |
| 4 | 24 | |
| 5 | 120 |
Factorial no es un buen caso para vender PD como optimización. La recursión no recalcula subproblemas; cada aparece una sola vez. La versión Bottom-Up elimina pila, pero no cambia el orden temporal: sigue siendo .
Fibonacci: recursión ingenua vs Programación Dinámica
Fibonacci es el ejemplo canónico de Programación Dinámica porque la recursión ingenua recalcula los mismos subproblemas muchas veces. Aquí sí hay una mejora real: se pasa de crecimiento exponencial a crecimiento lineal.
Paso 1 — Modelo recursivo de Fibonacci
El estado se identifica con un solo parámetro: .
Son valores conocidos directamente.
Cada estado depende de dos estados anteriores.
El estado pedido es la respuesta final.
Recurrencia completa
En este modelo, es el parámetro de estado que representa el índice del número de Fibonacci que queremos calcular. Los casos base y detienen la recursión, mientras que la transición suma los dos subproblemas inmediatamente anteriores.
Subproblemas repetidos en Fib(5)
Paso 2 — Traducción al modelo de PD
Cada posición guarda un Fibonacci ya calculado.
Los casos base del modelo recursivo se convierten en celdas iniciales.
El caso general se convierte en una fórmula de llenado.
Para calcular ya deben existir y .
La última celda contiene el Fibonacci pedido.
Modelo de PD completo
Cada celda de la tabla almacena el valor óptimo ya calculado para el subproblema . El uso de la tabla elimina la necesidad de volver a calcular el mismo valor múltiples veces.
Modelo Top-Down (mismo modelo, con memoización)
En el enfoque Top-Down, la estructura sigue siendo recursiva, pero se intercala un paso de consulta a la memoria (memoización) antes de realizar cualquier llamado recursivo.
fibTD(n, Tabla[n]) BEGIN
IF (n = 0 OR n = 1) THEN BEGIN
RETURN n;
END
IF (Tabla[n] != -1) THEN BEGIN
RETURN Tabla[n];
END
Tabla[n] <- fibTD(n - 1, Tabla) + fibTD(n - 2, Tabla);
RETURN Tabla[n];
ENDfibBU(n) BEGIN
Tabla[0] <- 0;
Tabla[1] <- 1;
FOR i <- 2 TO n DO BEGIN
Tabla[i] <- Tabla[i - 1] + Tabla[i - 2];
END
RETURN Tabla[n];
ENDComparación de Fibonacci ingenuo vs PD
| Versión | Idea | Tiempo | Espacio | Comentario |
|---|---|---|---|---|
| Recursiva ingenua | Llama Fib(n−1) y Fib(n−2) sin memoria. | Recalcula muchos subproblemas. | ||
| Top-Down con memoización | Conserva recursión, pero guarda estados. | Calcula cada Fib(k) una sola vez, pero mantiene pila. | ||
| Bottom-Up | Llena desde Fib(0) hasta Fib(n). | Evita recursión y facilita optimización espacial. | ||
| Bottom-Up optimizado | Solo guarda los dos últimos valores. | No permite reconstruir tabla completa, pero basta para el valor. |
fibBUOptimizado(n) BEGIN
IF (n = 0 OR n = 1) THEN BEGIN
RETURN n;
END
anterior2 <- 0;
anterior1 <- 1;
FOR i <- 2 TO n DO BEGIN
actual <- anterior1 + anterior2;
anterior2 <- anterior1;
anterior1 <- actual;
END
RETURN actual;
ENDFibonacci sí justifica Programación Dinámica: hay subproblemas superpuestos, cada estado se calcula una sola vez y el tiempo baja de a . Esta es la diferencia real entre recursión ingenua y PD.
Cierre de la parte 1
- Verifica que el problema tenga subestructura óptima y subproblemas superpuestos.
- Define el modelo recursivo: estado, casos base, transición y respuesta final.
- Convierte el estado en tabla o memoria.
- Convierte los casos base en inicialización.
- Convierte el caso general en fórmula de llenado.
- Escoge Top-Down si quieres conservar la recursión; escoge Bottom-Up si quieres controlar el orden de cálculo.
- Si necesitas la solución explícita, guarda decisiones en una tabla de caminos y reconstruye hacia atrás.
La segunda parte debe continuar con comparación formal Top-Down vs Bottom-Up, LCS completo, patrones de modelado en 1D/2D, reconstrucción de solución, errores típicos y resumen operativo final.
Programación Dinámica — Parte 2
Comparación de enfoques, LCS completo, reconstrucción de soluciones, patrones y errores típicos.
Comparación Top-Down vs Bottom-Up
Top-Down y Bottom-Up resuelven el mismo modelo de Programación Dinámica, pero recorren los estados de forma distinta. Top-Down conserva la recursión y calcula bajo demanda. Bottom-Up elimina la recursión y llena la tabla en un orden explícito.
Comparación Top-Down vs Bottom-Up
| Aspecto | Top-Down (Memoización) | Bottom-Up (Iterativo) |
|---|---|---|
| Implementación | Recursiva. Modifica el recursivo original agregando memoria. | Iterativa. Usa ciclos para llenar tablas. |
| Orden de resolución | Resuelve solo los subproblemas necesarios. | Resuelve todos los subproblemas definidos por la tabla. |
| Patrón de llenado | Puede ser irregular y dependiente de la entrada. | Suele ser regular: filas, columnas, diagonales o longitudes crecientes. |
| Tiempo | Mismo orden asintótico sobre los estados efectivamente resueltos. | Mismo orden asintótico si se llenan los mismos estados. |
| Espacio | Tabla o diccionario de memoización más pila recursiva. | Solo tablas, sin pila recursiva. |
| Constantes | Peores: entrar y salir de cada ambiente recursivo cuesta. | Mejores: no hay overhead de pila. |
| Veredicto de uso | Útil cuando el patrón de estados es irregular. | Preferible cuando el patrón de llenado es regular. |
Si puedes identificar un orden claro de llenado de la tabla, usa Bottom-Up. Si no puedes porque los estados dependen de la entrada o aparecen de forma irregular, usa Top-Down. No es una decisión estética: es una decisión de dependencias.
Decisión entre Top-Down y Bottom-Up
Dos versiones pueden tener el mismo orden asintótico y aun así tener tiempos reales distintos. Bottom-Up suele tener mejores constantes porque evita el costo fijo de crear y destruir ambientes recursivos.
LCS — Planteamiento del problema
Dadas dos cadenas de longitud y de longitud , el problema LCS busca la longitud de la subsecuencia más larga que aparece en ambas cadenas.
Una subsecuencia conserva el orden relativo de los caracteres, pero no exige que estén consecutivos. Por eso puede ser subsecuencia de .
Ejemplo del curso
| Elemento | Valor |
|---|---|
| Cadena X | AACCCGGTAGTA con |
| Cadena Y | GCAATTTGGGGCTA con |
| Resultado esperado |
Una subcadena debe ser consecutiva. Una subsecuencia no. LCS trabaja con subsecuencias, por eso puede saltar caracteres mientras conserve el orden.
LCS — Paso 1: construir el modelo recursivo
Como en cualquier problema de PD, primero se modela recursivamente. En LCS se comparan los últimos caracteres de los prefijos y .
representa la longitud de la subsecuencia común más larga entre los prefijos y .
Modelo recursivo de LCS
Si alguna cadena está vacía, no hay caracteres comunes.
Si los últimos caracteres coinciden, ese carácter puede formar parte de una LCS.
Si no coinciden, se prueba descartar el último carácter de una cadena o de la otra.
El problema original usa las dos cadenas completas.
Recurrencia completa
Aquí, los parámetros y indican los prefijos de las cadenas e . La condición suma 1 al óptimo anterior porque hemos encontrado un carácter común. Si no coinciden, usamos para elegir la mejor decisión entre avanzar en una cadena u otra.
Si la LCS óptima de dos prefijos incluye el carácter final común, la parte anterior también debe ser una LCS óptima de los prefijos anteriores. Si no lo fuera, podría reemplazarse por una mejor y se obtendría una LCS total más larga, contradicción.
El mismo par puede alcanzarse desde varias ramas de la recursión. Esa repetición es exactamente lo que justifica Programación Dinámica.
LCS — Paso 2: traducir el modelo a PD
Después de escribir el modelo recursivo, cada estado se convierte en una celda .
Traducción del modelo recursivo a PD
| Modelo recursivo | Modelo de PD | Comentario |
|---|---|---|
| Valor óptimo del subproblema de prefijos. | ||
| Primera columna inicializada en cero. | ||
| Primera fila inicializada en cero. | ||
| Movimiento diagonal; se guarda coincidencia. | ||
| Se toma la mejor alternativa entre descartar de X o descartar de Y. |
Modelo de PD completo
La celda de la tabla almacena la longitud de la LCS calculada hasta los prefijos actuales. Al igual que en el modelo recursivo, si hay un fallo de coincidencia, la función nos permite conservar el mejor valor alcanzado en los estados vecinos.
Tablas usadas en LCS
| Tabla | Contenido | Necesaria para |
|---|---|---|
| Longitud óptima de cada subproblema. | Conocer la longitud final de la LCS. | |
| Decisión que produjo cada celda: diagonal, arriba o izquierda. | Reconstruir una LCS concreta. |
La celda devuelve la longitud de la LCS. Si quieres la subsecuencia, necesitas reconstruir el camino. No confundas el valor óptimo con la solución explícita.
LCS — Pasos 3 y 4: inicializar y llenar la tabla
La inicialización viene directamente del caso base: si un prefijo tiene longitud cero, la LCS mide cero.
Inicialización
Una cadena vacía no tiene caracteres comunes con ningún prefijo de .
Ningún prefijo de tiene subsecuencia común no vacía con una cadena vacía.
Llenado Bottom-Up por filas
Cada fila representa un prefijo de .
Cada columna representa un prefijo de .
Se usa la diagonal porque ambos caracteres se incorporan a la subsecuencia.
Se toma la mejor alternativa entre ignorar el carácter actual de o ignorar el carácter actual de .
Dependencias de una celda dp[i][j]
LCS(X[m], Y[n]) BEGIN
FOR i <- 0 TO m DO BEGIN
dp[i][0] <- 0;
END
FOR j <- 0 TO n DO BEGIN
dp[0][j] <- 0;
END
FOR i <- 1 TO m DO BEGIN
FOR j <- 1 TO n DO BEGIN
IF (X[i] = Y[j]) THEN BEGIN
dp[i][j] <- dp[i - 1][j - 1] + 1;
path[i][j] <- "diagonal";
END
ELSE BEGIN
IF (dp[i - 1][j] >= dp[i][j - 1]) THEN BEGIN
dp[i][j] <- dp[i - 1][j];
path[i][j] <- "arriba";
END
ELSE BEGIN
dp[i][j] <- dp[i][j - 1];
path[i][j] <- "izquierda";
END
END
END
END
RETURN dp[m][n];
ENDLa tabla tiene celdas y cada celda se llena en tiempo constante. Por tanto, el tiempo es y el espacio es si se guarda toda la tabla.
LCS — Paso 5: tabla de óptimos
La tabla de óptimos no es una tabla decorativa: cada celda tiene significado. responde cuál es la longitud de la LCS entre los primeros caracteres de y los primeros caracteres de
Mini ejemplo de tabla LCS para X = ABC y Y = AC
| X/Y | ∅ | A | C |
|---|---|---|---|
| ∅ | 0 | 0 | 0 |
| A | 0 | 1 | 1 |
| B | 0 | 1 | 1 |
| C | 0 | 1 | 2 |
Lectura del mini ejemplo
La diagonal suma 1 porque los caracteres coinciden.
No hay coincidencia, así que se conserva el mejor valor anterior.
La LCS final tiene longitud 2: AC.
Para AACCCGGTAGTA y GCAATTTGGGGCTA, la longitud de la subsecuencia común más larga es .
LCS — Paso 6: reconstruir la solución
El recorrido hacia atrás empieza en . Desde ahí se siguen las decisiones hasta llegar a la primera fila o primera columna.
Movimientos de reconstrucción
| Movimiento | Condición | Acción |
|---|---|---|
| Diagonal | Agregar ese carácter a la solución y mover a . | |
| Arriba | Mover a descartando . | |
| Izquierda | Mover a descartando . |
reconstruirLCS(X[m], Y[n], dp[m][n]) BEGIN
i <- m;
j <- n;
solucion <- "";
WHILE (i > 0 AND j > 0) DO BEGIN
IF (X[i] = Y[j]) THEN BEGIN
solucion <- X[i] + solucion;
i <- i - 1;
j <- j - 1;
END
ELSE BEGIN
IF (dp[i - 1][j] >= dp[i][j - 1]) THEN BEGIN
i <- i - 1;
END
ELSE BEGIN
j <- j - 1;
END
END
END
RETURN solucion;
ENDEsquema matemático del recorrido
Se empieza desde el problema completo.
El carácter agregado debe ponerse al inicio de la solución acumulada porque el recorrido va hacia atrás.
Ese vecino representa el subproblema que conservó el óptimo.
Al llegar a una cadena vacía, ya no se pueden agregar más caracteres.
Cuando , hay empate. Elegir arriba o izquierda puede reconstruir una LCS distinta, pero con la misma longitud óptima. Eso no es error.
El recorrido hacia atrás da como máximo pasos, porque en cada movimiento disminuye , o ambos.
LCS Top-Down con memoización
La versión Top-Down copia casi literalmente el modelo recursivo. La única diferencia es que antes de resolver un estado revisa si ya está guardado.
Modelo Top-Down (mismo modelo, con memoización)
El modelo Top-Down mantiene la estructura recursiva original, pero utiliza una tabla de memoización para almacenar y reutilizar los resultados de los subproblemas ya resueltos, evitando así el costo exponencial de los cálculos repetidos.
LCSTD(X[m], Y[n], i, j, memo[m][n]) BEGIN
IF (i = 0 OR j = 0) THEN BEGIN
RETURN 0;
END
IF (memo[i][j] != -1) THEN BEGIN
RETURN memo[i][j];
END
IF (X[i] = Y[j]) THEN BEGIN
memo[i][j] <- 1 + LCSTD(X, Y, i - 1, j - 1, memo);
END
ELSE BEGIN
a <- LCSTD(X, Y, i - 1, j, memo);
b <- LCSTD(X, Y, i, j - 1, memo);
memo[i][j] <- max(a, b);
END
RETURN memo[i][j];
ENDHay como máximo estados distintos. Cada estado se calcula una sola vez. Por tanto, el tiempo es . El espacio es por memoización más pila recursiva de profundidad .
Para LCS clásico, Bottom-Up es más natural porque el patrón de llenado es rectangular y regular. Top-Down sirve mejor para enseñar el vínculo con el modelo recursivo o para variantes donde no todos los estados se visitan.
Patrones de modelado en Programación Dinámica
La dimensión de la tabla no se decide al azar. Sale directamente del estado. Un estado con un parámetro suele producir una tabla 1D; un estado con dos parámetros suele producir una tabla 2D.
Patrones frecuentes de modelado
| Patrón | Estado típico | Tabla | Ejemplos |
|---|---|---|---|
| Secuencia 1D | Fibonacci, escalones, suma máxima lineal. | ||
| Dos prefijos | LCS, distancia de edición, alineamiento de cadenas. | ||
| Índice y capacidad | Mochila 0/1, cambio de monedas. | ||
| Intervalos | Multiplicación de matrices, palíndromos, árboles óptimos. | ||
| Estado con decisión anterior | Problemas con restricciones de tomar/no tomar consecutivamente. |
- El estado debe contener toda la información necesaria para decidir el subproblema.
- El estado no debe contener información que pueda derivarse de otros parámetros.
- La transición debe apuntar a estados más pequeños o ya calculados.
- La respuesta final debe estar identificada antes de escribir código.
Optimización espacial en PD
Muchas tablas de PD parecen necesitar toda la matriz, pero para calcular el valor final a veces basta conservar una parte pequeña. La regla es mirar las dependencias de cada celda.
Cuándo se puede reducir espacio
| Caso | Dependencia | Espacio reducido | Cuidado |
|---|---|---|---|
| Fibonacci | Solo sirve si se necesita el valor final. | ||
| LCS valor | Fila anterior y fila actual. | Se pierde el camino completo si no se guarda información extra. | |
| Mochila 0/1 valor | Fila anterior o vector recorrido en orden correcto. | El orden del ciclo interno puede cambiar el significado del algoritmo. |
Primero diseña la tabla completa. Luego, si las dependencias lo permiten, reduces espacio. Optimizar antes de tener claro el modelo es una receta para dañar la transición.
Errores típicos en Programación Dinámica
Errores frecuentes en Programación Dinámica
| Error | Por qué está mal | Corrección |
|---|---|---|
| Empezar por la tabla sin modelo recursivo | La tabla no tiene significado si no se sabe qué representa cada celda. | Definir primero estado, caso base, transición y respuesta final. |
| Usar PD sin subproblemas superpuestos | Si no hay recálculo, la memoria no aporta valor real. | Usar recursión, iteración o Divide y Vencerás según corresponda. |
| Inicializar mal los casos base | Toda la tabla hereda el error desde la primera fila, columna o celda. | Copiar los casos base exactos del modelo recursivo. |
| Llenar la tabla en orden incorrecto | Una celda puede depender de otra que todavía no existe. | Elegir orden por filas, columnas, diagonales o longitudes según dependencias. |
| No guardar caminos cuando se necesita la solución | El valor óptimo no dice qué decisiones lo produjeron. | Guardar tabla de caminos o reconstruir con reglas equivalentes. |
| Optimizar espacio y luego querer reconstruir | Si borraste las filas anteriores, perdiste parte del camino. | Decidir desde el inicio si necesitas valor o solución explícita. |
| Confundir subsecuencia con subcadena en LCS | Cambia completamente la transición. | Recordar: subsecuencia conserva orden, pero permite saltos. |
Resumen operativo final
- Verifica que el problema tenga subestructura óptima.
- Verifica que existan subproblemas superpuestos.
- Define el estado con los parámetros mínimos necesarios.
- Escribe casos base.
- Escribe la transición recursiva.
- Identifica la respuesta final.
- Convierte el estado en tabla o memoria.
- Inicializa la tabla usando los casos base.
- Define el orden de llenado respetando dependencias.
- Llena la tabla.
- Si se necesita la solución explícita, guarda caminos y reconstruye hacia atrás.
- Analiza tiempo como número de estados por costo de transición.
- Analiza espacio como tamaño de tabla, memoización, caminos y pila si aplica.
Fórmulas clave
En PD no se analiza solo la profundidad recursiva; se cuentan estados únicos.
Estado 1D con transición constante; tiempo .
Estado 2D con transición constante; tiempo .
Si solo se necesita la longitud, puede reducirse espacio; si se necesita reconstrucción, normalmente se conserva la tabla o caminos.