Dynamic Programming · Dynamic Programming Fundamentals

DP: 1D Tabulation

Building an answer from a small window of previously-computed answers instead of full recursive re-computation.

  • 5 min read
  • 12 practice questions
  • 4 graded problems
On this page
  1. Description
  2. The approach
  3. A second flavor: a choice at every step
  4. Where you'll see it
  5. Which tool for which job?
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

Some problems have answers built from answers to smaller versions of themselves. The ways to climb n stairs are the ways to climb n - 1 plus the ways to climb n - 2. Written as plain recursion, the same smaller answers get recomputed an enormous number of times. Naive fib(30) makes 2,692,537 calls to compute 31 distinct values. Dynamic programming (DP) computes each distinct subproblem once and reuses it. This skill covers the one-dimensional case: a state, a recurrence, base cases, and a table filled in order.

We'll go from simple to more technical:

  1. The pattern: define dp[i], write the recurrence, seed the base cases, fill in order.
  2. A second flavor: a choice at every step (House Robber), where greedy fails.
  3. Where DP shows up in production (pricing, parsing, planning).
  4. What breaks it: no caching, wrong base cases, sentinels that overflow, and recursion depth.

The approach

Three decisions, in this order:

  1. State - what dp[i] means, in words. ("dp[i] = number of ways to reach step i.")
  2. Recurrence - how dp[i] follows from smaller states. ("dp[i] = dp[i - 1] + dp[i - 2].")
  3. Base cases - the smallest states, answered directly. ("dp[0] = 1, dp[1] = 1.")

Java

int climbStairs(int n) {
    int prev2 = 1, prev1 = 1;              // dp[0] and dp[1]
    for (int i = 2; i <= n; i++) {
        int curr = prev1 + prev2;          // dp[i] = dp[i - 1] + dp[i - 2]
        prev2 = prev1;
        prev1 = curr;
    }
    return prev1;
}

Python

def climb_stairs(n: int) -> int:
    prev2, prev1 = 1, 1                        # dp[0] and dp[1]
    for i in range(2, n + 1):
        prev2, prev1 = prev1, prev1 + prev2    # dp[i] = dp[i - 1] + dp[i - 2]
    return prev1

This is tabulation: fill answers from the base cases upward, in an order where everything dp[i] reads is already computed. Because the recurrence reads only the last two values, two variables replace the whole table - O(1) space. The alternative, memoization, keeps the natural recursion and caches each result the first time it's computed; it produces the same answers and only visits states that are actually reachable (coin change with coins {5, 10} and amount 1,000 touches 200 states, not 1,001), at the cost of recursion depth.

A second flavor: a choice at every step

Often the recurrence isn't a sum but a choice. House Robber - take values from an array, never two adjacent, maximize the total - asks at each house: skip it, or take it on top of the best total two houses back.

Java

int rob(int[] nums) {
    int twoBack = 0, oneBack = 0;          // best totals up to i - 2 and i - 1
    for (int x : nums) {
        int best = Math.max(oneBack, twoBack + x);   // skip house i, or take it
        twoBack = oneBack;
        oneBack = best;
    }
    return oneBack;
}

Python

def rob(nums: list[int]) -> int:
    two_back, one_back = 0, 0                  # best totals up to i - 2 and i - 1
    for x in nums:
        best = max(one_back, two_back + x)     # skip house i, or take it
        two_back, one_back = one_back, best
    return one_back

Greedy shortcuts fail on this kind of problem. Taking every other house misses [2, 1, 1, 2] (the answer 4 uses the first and last), and "always take the largest coin" misses coins {1, 3, 4} for 6 (3 + 3 beats 4 + 1 + 1). When a local choice can block a better global answer, DP compares the choices instead of committing to one.

Where you'll see it

Text layout engines break paragraphs into lines with DP (TeX's line breaking minimizes total "badness" over all break points), diff tools align files with DP, speech and handwriting recognizers decode with the Viterbi algorithm (DP over time steps), and resource planners compute cheapest schedules by DP over days or budget. In each, the state is small, the recurrence is local, and the answers are reused many times.

Which tool for which job?

You needUse
Overlapping subproblems with a 1D statea table filled from the base cases up
Only the last few values matterrolling variables, O(1) space
Few states actually reachabletop-down memoization
A huge n with a linear recurrencematrix exponentiation or fast doubling, O(log n)
How the answer was reachedkeep the full table and trace back
A local choice that can block a better answerDP, not greedy

Common mistakes

MistakeWhat happensDo this instead
Recursion without cachingnaive fib(30) makes 2,692,537 callsmemoize or tabulate
Wrong base casesevery later value is wrongcheck the smallest inputs by hand
Integer.MAX_VALUE as "impossible"+ 1 wraps negative and wins every minamount + 1, or check before adding
long counts that grow like Fibonacciclimbing stairs wraps at n = 92BigInteger, or the modulus the problem gives
Deep top-down recursionstack overflow / RecursionErrorbottom-up

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. Why does naive recursive Fibonacci take exponential time, and what fixes it?

Show answer

fib(n) calls fib(n - 1) and fib(n - 2), which call overlapping subproblems again and again - fib(30) makes 2,692,537 calls for 31 distinct values. Caching each value (memoization) or computing them bottom-up (tabulation) solves each subproblem once: O(n).

2. Coin change fills dp with Integer.MAX_VALUE for "impossible" and computes dp[i - c] + 1. What goes wrong?

Show answer

Integer.MAX_VALUE + 1 wraps to Integer.MIN_VALUE, a huge negative number that wins every min. For coins {2} and amount 3, the answer comes out as -2,147,483,648 instead of -1. Use a sentinel like amount + 1 (more coins than could ever be needed) or skip impossible states before adding.

3. When can a DP keep two variables instead of an array?

Show answer

When each state reads only the previous one or two states, and you only need the final value. If you also need to reconstruct the choices (which houses were robbed), keep the full table or parent pointers - rolling variables forget how each value was reached.

Key takeaways

  • DP = state + recurrence + base cases; get the state's meaning written down first.
  • Each distinct subproblem is solved once - exponential recursion becomes polynomial.
  • Memoization (top-down, only reachable states, uses the stack) and tabulation (bottom-up, no recursion limit) give the same answers.
  • Keep only what the recurrence reads - O(1) space when it looks back a fixed distance.
  • Choose sentinels for "impossible" that can't overflow, and watch the answer's own size.

Quick revise

The whole skill on one page, for the night before an interview.

This page puts the lesson and the deep dive together. Skim it before practice or an interview, and go back to the full lesson for anything you don't recognize.

Pick the tool

You needUse
1D overlapping subproblemstable from the base cases
Last few values onlyrolling variables
Sparse reachable statesmemoization
n around 10^18matrix power / fast doubling
How, not just how muchfull table plus traceback

Snippets worth knowing by heart

Java

int[] dp = new int[amount + 1];
Arrays.fill(dp, amount + 1);                        // "impossible" that can't overflow
dp[0] = 0;
for (int v = 1; v <= amount; v++)
    for (int c : coins) if (c <= v) dp[v] = Math.min(dp[v], dp[v - c] + 1);
return dp[amount] > amount ? -1 : dp[amount];

Python

two_back, one_back = 0, 0                # House Robber, O(1) space
for x in nums:
    two_back, one_back = one_back, max(one_back, two_back + x)

The traps

  • No caching: naive fib(30) makes about 2.7 million calls. Memoize or tabulate.
  • Wrong base cases: every later value inherits the error. Check the smallest inputs by hand.
  • Overflowing sentinels: Integer.MAX_VALUE + 1 wraps negative and wins every min. Use amount + 1 as impossible.
  • long counts past n = 91: climbing-stairs counts wrap at n = 92. Use BigInteger or the given modulus.
  • Deep top-down recursion: large n overflows the stack. Go bottom-up.

Staff-level points, one line each

  • fib(10^18) mod 1e9+7 took 84 matrix multiplications (209783453).
  • Coins {700, 1300, 2900} to 1,000,000: the memo touched 9,973 states, the table filled 1,000,001; both answered 348.
  • Climbing stairs in a long: ways(91) = 7540113804746346429, and it wraps at 92. In Python, ways(1000) has 209 digits.
  • Top-down climb(100,000) overflowed Java's stack; @cache climb(5000) raised RecursionError.
  • A circular street means two linear DPs.

Say it in an interview

  • "State, recurrence, base cases, order. Each state is computed once."
  • "Greedy fails here because one local choice can block a better total."
  • "If n is astronomically large and the recurrence is linear, I'd use a matrix power."
  • "I'd write it bottom-up so depth isn't an issue."

Practice

This skill comes with 12 multiple-choice questions that tell you which misconception a wrong answer reveals and 4 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice DP: 1D Tabulation free

Climbing Stairs

EasyFreeCoding problemJava · Python

A staircase has n steps. With each stride you go up either one step or two. Count the different sequences of strides that take you from the bottom to the top.

Coin Change: Minimum Coins

MediumProCoding problemJava · Python

You have an unlimited supply of coins of each value in coins. Make exactly amount with as few coins as possible and return that number, or -1 if amount can't be made.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

House Robber

MediumProCoding problemJava · Python

Each house on a street holds some cash, nums[i]. Any two houses side by side share an alarm, so taking from both sets it off. What's the largest total you can collect without taking from two neighbors?

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Decode Ways

MediumProCoding problemJava · Python

Letters were turned into numbers with A = 1, B = 2, ..., Z = 26, and the numbers were then written next to each other with no separators. Given that string of digits, count how many letter strings it could have come from. A code can't start with 0, so 06 can't stand for F.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice DP: 1D Tabulation free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime