MedioAlgoritmos#recursion#programacion-dinamica#memoizacion

Subir escalones

Actualizado22 jul 2026

Pista 1

Para llegar al escalón n, tu último paso vino del n−1 (paso de 1) o del n−2 (paso de 2). ¿Qué relación te da eso?

Pista 2

formas(n) = formas(n−1) + formas(n−2). ¿Te suena esa recurrencia de algo?

Pista 3

La recursión directa recalcula los mismos subproblemas una y otra vez: dibuja el árbol de llamadas de formas(5) y cuenta cuántas veces aparece formas(2).

Estás al pie de una escalera de n escalones. En cada paso puedes subir 1 o 2 escalones. ¿De cuántas formas distintas puedes llegar arriba?

Ejemplos

formas(1) → 1    // [1]
formas(2) → 2    // [1+1], [2]
formas(3) → 3    // [1+1+1], [1+2], [2+1]
formas(4) → 5    // [1+1+1+1], [1+1+2], [1+2+1], [2+1+1], [2+2]
formas(10) → 89

Este es el problema-puerta de la programación dinámica: la solución correcta está a un paso de la recursión que ya conoces, y el salto entre ambas es la diferencia entre un programa que termina en microsegundos y uno que no termina.

Ver solución

La recurrencia. Para pisar el escalón n, tu último paso salió del n−1 o del n−2. Esas dos opciones no se solapan y cubren todos los casos, así que:

formas(n) = formas(n−1) + formas(n−2)

— la recurrencia de Fibonacci, disfrazada de escalera.

Por qué la recursión directa explota. formas(50) con la recursión ingenua hace ~2⁵⁰ llamadas, porque recalcula los mismos subproblemas una y otra vez (formas(2) aparece cientos de miles de veces en el árbol). Complejidad O(2ⁿ): inviable.

Arreglo 1 — memoización (la recursión, más una caché):

function formas(n: number, memo = new Map<number, number>()): number {
  if (n <= 2) return n;
  if (memo.has(n)) return memo.get(n)!;
  const r = formas(n - 1, memo) + formas(n - 2, memo);
  memo.set(n, r);
  return r;
}

Cada subproblema se calcula una vez: O(n) en tiempo, O(n) en espacio. La estructura recursiva del razonamiento se conserva intacta.

Arreglo 2 — iterativo con dos variables (la versión pulida):

function formas(n: number): number {
  if (n <= 2) return n;
  let dosAtras = 1; // formas(1)
  let unaAtras = 2; // formas(2)
  for (let i = 3; i <= n; i++) {
    [dosAtras, unaAtras] = [unaAtras, dosAtras + unaAtras];
  }
  return unaAtras;
}

Para calcular formas(i) solo hacen falta los dos valores anteriores — no toda la tabla: O(n) en tiempo, O(1) en espacio.

La lección transferible: cuando una recursión es correcta pero lenta, pregúntate si repite subproblemas. Si sí — memoización primero (mecánica, sin re-pensar nada), versión iterativa después si necesitas exprimir memoria. Ese par de movimientos es la programación dinámica.

Volver