Longest Collatz Sequence
La secuencia de Collatz sigue estas reglas: si n es par, divide entre 2;
si n es impar, multiplica por 3 y suma 1.
¿Qué número inicial menor que un millón produce la cadena más larga?
1 La Conjetura de Collatz
También llamada "problema 3n+1", es uno de los problemas abiertos más famosos de las matemáticas. Nadie ha demostrado que siempre llegue a 1, pero tampoco se ha encontrado un contraejemplo.
Ejemplo: Secuencia comenzando en 13
● Impar (×3+1) ● Par (÷2) ● Fin
¿Por qué es un problema abierto?
La secuencia es caótica. Números cercanos pueden tener comportamientos muy diferentes:
Erdős ofreció $500 por una demostración. Dijo: "Las matemáticas aún no están listas para este tipo de problemas."
El Problema: Trabajo Repetido
Si calculamos las secuencias para 13, 26, 40, 80... ¡repetimos mucho trabajo!
El Insight: Memoización
Si ya calculamos que collatz(40) = 9 pasos, ¿por qué calcularlo de nuevo?
Memoización = guardar resultados para no recalcular.
Es la base de la programación dinámica.
2 Fuerza Bruta (Sin Caché)
La solución directa: para cada número del 1 al 999,999, calcular la longitud de su secuencia completa. Problema: repetimos muchísimos cálculos.
¿Por qué es lento?
Cuando calculamos collatz_length(26), pasamos por 40, 20, 10, 5, 16, 8, 4, 2, 1.
Pero cuando calculamos collatz_length(40), ¡repetimos exactamente lo mismo desde 40!
# Trabajo repetido:
collatz(26): 26 → 13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1
collatz(40): 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1
collatz(80): 80 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1
Análisis de complejidad
n números × L pasos promedio por secuencia
Solo variables locales
L puede ser muy grande (hasta ~500 para n < 1M) y muchas secuencias comparten sufijos.
3 Memoización: Recordar para no Repetir
La idea es simple: cuando calculemos collatz(40) = 9, lo guardamos.
La próxima vez que lleguemos a 40, devolvemos 9 directamente.
¿Qué es la Memoización?
Memoización viene de "memo" (memorando). Es una técnica de optimización que:
- Guarda el resultado de funciones costosas
- Devuelve el resultado guardado cuando se llama con los mismos argumentos
Es útil cuando una función:
- Es pura (mismo input → mismo output)
- Se llama muchas veces con los mismos argumentos
- Es costosa de calcular
Así funciona el caché:
■ Cache hits: cuando calculamos 26, llegamos a 40 y ya sabemos que son 9 pasos más.
Comparación de rendimiento
| Método | Tiempo (1M números) | Memoria |
|---|---|---|
| Sin caché | ~30 segundos | O(1) |
| Con memoización | ~0.5 segundos | O(n) caché |
La memoización es un trade-off: usamos más memoria para ahorrar tiempo. En este caso, ~2MB de caché nos ahorra ~30 segundos.
¿Cuándo usar memoización?
✓ Usar cuando:
- La función es pura (sin efectos secundarios)
- Se llama con los mismos argumentos múltiples veces
- El cálculo es costoso
- Tienes memoria suficiente para el caché
✗ No usar cuando:
- Cada llamada tiene argumentos únicos
- El cálculo es trivial (O(1))
- La memoria es limitada
@cache vs @lru_cache
@cache
Guarda todo para siempre. Ideal cuando la memoria no es problema.
from functools import cache
@lru_cache(maxsize=N)
Guarda los últimos N resultados (Least Recently Used). Ideal cuando la memoria es limitada.
from functools import lru_cache