Maximum Path Sum I
Empezando en la cima del triángulo y moviéndote a números adyacentes en la fila de abajo, encuentra la suma máxima desde la cima hasta la base.
1 El Problema del Triángulo
Tenemos un triángulo de números. Desde la cima, podemos bajar a uno de los dos números adyacentes en la fila siguiente. Queremos maximizar la suma del camino.
Ejemplo pequeño (4 filas):
Camino óptimo: 3 → 7 → 4 → 9 = 23
¿Qué significa "adyacente"?
Desde cada número, solo puedes moverte a los dos números directamente debajo:
Si estás en posición i de una fila,
puedes ir a posición i o i+1 de la siguiente.
Desde el 7, solo puedes ir al 2 o al 4 (no al 6)
¿Por qué NO funciona elegir siempre el mayor?
El enfoque "greedy" (codicioso) sería: en cada paso, elegir el número más grande. Pero esto puede llevarnos a un camino subóptimo:
3→7→4→5 = 19
3→7→4→9 = 23
Greedy elige 5 sobre 9 porque desde el 4, el 5 está "disponible" pero el camino al 9 requiere planificación desde antes.
El Triángulo Real (15 filas)
El problema nos da un triángulo de 15 filas. ¿Cuántos caminos posibles hay?
14 decisiones (filas 1→2, 2→3, ..., 14→15).
Total: 214 = 16,384 caminos
Probar todos es factible para 15 filas, pero el problema 67 tiene ¡100 filas! (2⁹⁹ caminos)
2 Fuerza Bruta: Probar Todos los Caminos
La solución más directa: explorar todos los caminos posibles con recursión y quedarnos con el mejor.
La Idea Recursiva
Para cada celda, la mejor suma desde ahí hasta abajo es:
mejor(fila+1, col),
mejor(fila+1, col+1)
)
Caso base: en la última fila, mejor(fila, col) = valor[fila][col]
El Problema: Trabajo Repetido
Observa este triángulo de llamadas para el ejemplo pequeño:
max(0,0) = 3 + max(max(1,0), max(1,1))
│
├── max(1,0) = 7 + max(max(2,0), max(2,1))
│ │
│ ├── max(2,0) = 2 + max(max(3,0), max(3,1))
│ └── max(2,1) = 4 + max(max(3,1), max(3,2)) ← ¡REPETIDO!
│
└── max(1,1) = 4 + max(max(2,1), max(2,2))
│
├── max(2,1) = 4 + max(max(3,1), max(3,2)) ← ¡REPETIDO!
└── max(2,2) = 6 + max(max(3,2), max(3,3))
max(2,1) se calcula dos veces. En triángulos grandes,
el mismo subproblema se recalcula miles de veces.
Exponencial - cada nodo genera 2 llamadas
Profundidad del stack de recursión
3 Programación Dinámica: Bottom-Up
En vez de calcular desde arriba (y repetir trabajo), calculamos desde abajo hacia arriba. Cada celda solo se procesa una vez.
La Idea Clave
Empezamos por la penúltima fila. Para cada número, sumamos el mayor de sus dos hijos:
Repetimos hasta llegar a la cima. El número en la cima es la respuesta.
Ejemplo paso a paso:
Paso 0: Triángulo original
Paso 1: Procesar fila 3
2+max(8,5)=10, 4+max(5,9)=13, 6+max(9,3)=15
Paso 2: Procesar fila 2
7+max(10,13)=20, 4+max(13,15)=19
Paso 3: Procesar fila 1 (cima)
3+max(20,19)=23 ✓
Comparación de Complejidad
| Método | Tiempo | Espacio | 15 filas | 100 filas |
|---|---|---|---|---|
| Recursión | O(2ⁿ) | O(n) | 16,384 ops | 10³⁰ ops |
| DP bottom-up | O(n²) | O(n²) | 120 ops | 5,050 ops |
n = número de filas. DP es exponencialmente más rápido.
El Patrón de Programación Dinámica
- Identificar subproblemas solapados: los mismos cálculos se repiten
- Definir el estado: ¿qué información necesito? (fila, columna)
- Encontrar la recurrencia: dp[i][j] = valor + max(dp[i+1][j], dp[i+1][j+1])
- Establecer caso base: la última fila ya tiene los valores finales
- Elegir dirección: bottom-up suele ser más eficiente que top-down con memoización