Dynamic Programming

Dynamic programming solves problems by breaking them into overlapping subproblems and storing intermediate results to avoid recomputation. There are two primary approaches: top-down (memoization) and bottom-up (tabulation).

Top-Down Approach (Memoization)

Start from the highest value n and recurse downward, caching results in a data structure — typically a hash map — to avoid recomputing the same subproblems:

#include <unordered_map>
using namespace std;
 
class Solution {
public:
    unordered_map<int, int> hashMap;
    Solution() {
        hashMap[1] = 1;
        hashMap[2] = 2;
    }
 
    int climbStairs(int n) {
        if (hashMap.count(n))
            return hashMap[n];
        hashMap[n] = climbStairs(n - 1) + climbStairs(n - 2);
        return hashMap[n];
    }
};

Time: O(n), Space: O(n) (recursion stack + cache).

Bottom-Up Approach (Tabulation)

Solve iteratively from smallest to largest index, retaining only the minimum state required for the next step. This eliminates recursion overhead and reduces memory:

class Solution {
public:
    int climbStairs(int n) {
        if (n == 1) return 1;
        if (n == 2) return 2;
 
        int one = 1, two = 2, ways = 0;
        for (int i = 3; i <= n; ++i) {
            ways = one + two;
            one = two;
            two = ways;
        }
        return ways;
    }
};

Time: O(n), Space: O(1).

Choosing Between Approaches

AspectTop-DownBottom-Up
ImplementationRecursive, intuitiveIterative
SpaceO(n) cache + call stackO(1) to O(n) variables
When to useNatural recursion, sparse statesDense state space, memory pressure
RiskStack overflow for large nMust order subproblem dependencies

Bottom-up should be preferred wherever possible for its memory efficiency and avoidance of recursion overhead.

References