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.

Idea central

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

CriterioDivide y VencerásProgramación Dinámica
SubproblemasNormalmente independientes.Se repiten o se solapan.
RecálculoNo 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ípicaRecursión directa o división explícita.Memoización o tabulación.
EjemploMerge Sort.Fibonacci con memoización, LCS, mochila 0/1.
No todo algoritmo recursivo necesita PD

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

RequisitoSignificadoPregunta diagnósticaSi falla
Subestructura óptimaEl 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 optimalidadLa 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 superpuestosLos mismos subproblemas aparecen varias veces.¿La recursión vuelve a calcular estados idénticos?La memoización no aporta valor sustancial.
Modelo recursivo formulableLa 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.
Diagnóstico rápido
  • 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

EstructuraQué almacenaCuándo se llenaEjemplo
Tabla de óptimosEl mejor valor de cada subproblema.A medida que se resuelven los estados. guarda la longitud de la LCS.
Tabla de caminosLa decisión que llevó al óptimo.Junto con la tabla de óptimos.Guardar si se vino de arriba, izquierda o diagonal.
Vector SOALa secuencia final de decisiones.Al final, recorriendo hacia atrás.La subsecuencia reconstruida en LCS o los objetos elegidos en mochila.
Por qué se reconstruye hacia atrás

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.

No siempre necesitas las tres

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.

Regla dura

Sin modelo recursivo no hay PD defendible. Una tabla sin estado, transición y caso base es solo almacenamiento accidental.

Componentes del modelo recursivo

ComponentePreguntaResultado 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

Definir estado

El estado debe guardar solo la información necesaria. Si faltan parámetros, la transición queda ambigua; si sobran, la tabla se infla.

Definir casos base

Los casos base son las celdas iniciales de la tabla o los retornos directos de la memoización.

Definir transición

La transición debe usar estados más pequeños o previamente calculables.

Definir respuesta final

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 recursivoEn el modelo de PDPregunta de control
Estado Celda ¿Qué dimensiones necesita la tabla?
Caso baseInicialización de filas, columnas o posiciones base.¿Qué celdas ya se conocen antes del ciclo?
Caso generalFórmula de llenado.¿Qué celdas anteriores necesito para calcular esta?
Decisión óptimaComparació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

Renderizando diagrama...

Cuándo elegir Top-Down o Bottom-Up

CriterioTop-DownBottom-Up
Cercanía al modelo matemáticoMuy alta: casi copia la recursión.Media: exige definir orden iterativo.
Subproblemas realmente visitadosSolo calcula los que necesita.Puede llenar estados que quizá no se usen.
Control de memoriaUsa memo más pila recursiva.Permite optimización espacial más clara.
RiesgoStack 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

Estado

El estado solo necesita un parámetro: .

Caso base 0

Por definición, .

Caso base 1

También puede declararse como base para simplificar el código.

Caso general

El problema de tamaño depende de un único subproblema de tamaño .

Respuesta final

El estado original ya contiene la respuesta.

Factorial recursivo
factorial(n) BEGIN
  IF (n = 0 OR n = 1) THEN BEGIN
    RETURN 1;
  END
  RETURN n * factorial(n - 1);
END

Paso 2 — Traducción al modelo de PD

Tabla

Cada posición almacena el factorial de un valor intermedio.

Inicialización

Los casos base del modelo recursivo se convierten en celdas iniciales.

Transición

El caso general se convierte en una fórmula iterativa.

Orden de llenado

Se llena de menor a mayor porque depende de .

Respuesta

La última posición contiene el factorial pedido.

Factorial Top-Down con memoización
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];
END
Factorial Bottom-Up
factorialBU(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];
END

Seguimiento Bottom-Up para n = 5

iFórmulaValor
0Caso base1
1Caso base1
22
36
424
5120
Diagnóstico honesto

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

Estado

El estado se identifica con un solo parámetro: .

Casos base

Son valores conocidos directamente.

Caso general

Cada estado depende de dos estados anteriores.

Respuesta

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)

Renderizando diagrama...

Paso 2 — Traducción al modelo de PD

Tabla

Cada posición guarda un Fibonacci ya calculado.

Inicialización

Los casos base del modelo recursivo se convierten en celdas iniciales.

Transición

El caso general se convierte en una fórmula de llenado.

Orden de llenado

Para calcular ya deben existir y .

Respuesta

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.

Fibonacci Top-Down con memoización
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];
END
Fibonacci Bottom-Up
fibBU(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];
END

Comparación de Fibonacci ingenuo vs PD

VersiónIdeaTiempoEspacioComentario
Recursiva ingenuaLlama Fib(n−1) y Fib(n−2) sin memoria.Recalcula muchos subproblemas.
Top-Down con memoizaciónConserva recursión, pero guarda estados.Calcula cada Fib(k) una sola vez, pero mantiene pila.
Bottom-UpLlena desde Fib(0) hasta Fib(n).Evita recursión y facilita optimización espacial.
Bottom-Up optimizadoSolo guarda los dos últimos valores.No permite reconstruir tabla completa, pero basta para el valor.
Fibonacci Bottom-Up con espacio O(1)
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;
END
Conclusión

Fibonacci 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

Resumen operativo
  1. Verifica que el problema tenga subestructura óptima y subproblemas superpuestos.
  2. Define el modelo recursivo: estado, casos base, transición y respuesta final.
  3. Convierte el estado en tabla o memoria.
  4. Convierte los casos base en inicialización.
  5. Convierte el caso general en fórmula de llenado.
  6. Escoge Top-Down si quieres conservar la recursión; escoge Bottom-Up si quieres controlar el orden de cálculo.
  7. Si necesitas la solución explícita, guarda decisiones en una tabla de caminos y reconstruye hacia atrás.
Alcance de la parte 2

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

AspectoTop-Down (Memoización)Bottom-Up (Iterativo)
ImplementaciónRecursiva. Modifica el recursivo original agregando memoria.Iterativa. Usa ciclos para llenar tablas.
Orden de resoluciónResuelve solo los subproblemas necesarios.Resuelve todos los subproblemas definidos por la tabla.
Patrón de llenadoPuede ser irregular y dependiente de la entrada.Suele ser regular: filas, columnas, diagonales o longitudes crecientes.
TiempoMismo orden asintótico sobre los estados efectivamente resueltos.Mismo orden asintótico si se llenan los mismos estados.
EspacioTabla o diccionario de memoización más pila recursiva.Solo tablas, sin pila recursiva.
ConstantesPeores: 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.
Veredicto práctico

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

Renderizando diagrama...

Mismo orden no significa mismo tiempo real

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.

Subsecuencia

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

ElementoValor
Cadena XAACCCGGTAGTA con
Cadena YGCAATTTGGGGCTA con
Resultado esperado
No confundir subsecuencia con subcadena

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 .

Estado

representa la longitud de la subsecuencia común más larga entre los prefijos y .

Modelo recursivo de LCS

Caso base

Si alguna cadena está vacía, no hay caracteres comunes.

Caracteres iguales

Si los últimos caracteres coinciden, ese carácter puede formar parte de una LCS.

Caracteres distintos

Si no coinciden, se prueba descartar el último carácter de una cadena o de la otra.

Respuesta final

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.

Verificación del principio de optimalidad

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.

Dónde aparece el solapamiento

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 recursivoModelo de PDComentario
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

TablaContenidoNecesaria 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.
Valor no es solución

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

Primera fila

Una cadena vacía no tiene caracteres comunes con ningún prefijo de .

Primera columna

Ningún prefijo de tiene subsecuencia común no vacía con una cadena vacía.

Llenado Bottom-Up por filas

Recorrer filas

Cada fila representa un prefijo de .

Recorrer columnas

Cada columna representa un prefijo de .

Si coinciden

Se usa la diagonal porque ambos caracteres se incorporan a la subsecuencia.

Si no coinciden

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]

Renderizando diagrama...

LCS Bottom-Up
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];
END
Complejidad

La 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∅AC
∅000
A011
B011
C012

Lectura del mini ejemplo

Primera coincidencia

La diagonal suma 1 porque los caracteres coinciden.

Carácter B no mejora

No hay coincidencia, así que se conserva el mejor valor anterior.

Coincidencia final

La LCS final tiene longitud 2: AC.

Resultado del ejemplo del curso

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

MovimientoCondiciónAcción
DiagonalAgregar ese carácter a la solución y mover a .
ArribaMover a descartando .
IzquierdaMover a descartando .
Reconstrucción de una LCS
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;
END

Esquema matemático del recorrido

Inicio

Se empieza desde el problema completo.

Si hay coincidencia

El carácter agregado debe ponerse al inicio de la solución acumulada porque el recorrido va hacia atrás.

Si no hay coincidencia

Ese vecino representa el subproblema que conservó el óptimo.

Fin

Al llegar a una cadena vacía, ya no se pueden agregar más caracteres.

Puede haber varias LCS

Cuando , hay empate. Elegir arriba o izquierda puede reconstruir una LCS distinta, pero con la misma longitud óptima. Eso no es error.

Costo de reconstrucción

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.

LCS Top-Down
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];
END
Complejidad Top-Down

Hay 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 .

Cuándo preferir esta versión

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ónEstado típicoTablaEjemplos
Secuencia 1DFibonacci, escalones, suma máxima lineal.
Dos prefijosLCS, distancia de edición, alineamiento de cadenas.
Índice y capacidadMochila 0/1, cambio de monedas.
IntervalosMultiplicación de matrices, palíndromos, árboles óptimos.
Estado con decisión anteriorProblemas con restricciones de tomar/no tomar consecutivamente.
Reglas para elegir estado
  1. El estado debe contener toda la información necesaria para decidir el subproblema.
  2. El estado no debe contener información que pueda derivarse de otros parámetros.
  3. La transición debe apuntar a estados más pequeños o ya calculados.
  4. 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

CasoDependenciaEspacio reducidoCuidado
FibonacciSolo sirve si se necesita el valor final.
LCS valorFila anterior y fila actual.Se pierde el camino completo si no se guarda información extra.
Mochila 0/1 valorFila anterior o vector recorrido en orden correcto.El orden del ciclo interno puede cambiar el significado del algoritmo.
No optimices antes de entender

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

ErrorPor qué está malCorrección
Empezar por la tabla sin modelo recursivoLa 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 superpuestosSi 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 baseToda 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 incorrectoUna 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ónEl valor óptimo no dice qué decisiones lo produjeron.Guardar tabla de caminos o reconstruir con reglas equivalentes.
Optimizar espacio y luego querer reconstruirSi 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 LCSCambia completamente la transición.Recordar: subsecuencia conserva orden, pero permite saltos.

Resumen operativo final

Receta completa
  1. Verifica que el problema tenga subestructura óptima.
  2. Verifica que existan subproblemas superpuestos.
  3. Define el estado con los parámetros mínimos necesarios.
  4. Escribe casos base.
  5. Escribe la transición recursiva.
  6. Identifica la respuesta final.
  7. Convierte el estado en tabla o memoria.
  8. Inicializa la tabla usando los casos base.
  9. Define el orden de llenado respetando dependencias.
  10. Llena la tabla.
  11. Si se necesita la solución explícita, guarda caminos y reconstruye hacia atrás.
  12. Analiza tiempo como número de estados por costo de transición.
  13. Analiza espacio como tamaño de tabla, memoización, caminos y pila si aplica.

Fórmulas clave

Tiempo general

En PD no se analiza solo la profundidad recursiva; se cuentan estados únicos.

Fibonacci

Estado 1D con transición constante; tiempo .

LCS

Estado 2D con transición constante; tiempo .

Espacio LCS

Si solo se necesita la longitud, puede reducirse espacio; si se necesita reconstrucción, normalmente se conserva la tabla o caminos.