Dynamic Programming
Beginner 10 min read

1D DP (climbing stairs, house robber)

In one line

In 1D DP, dp[i] is the answer for the first i elements (or position i), and it is built from a few earlier entries such as dp[i-1] and dp[i-2].

What it is

One-dimensional DP problems have a single index as the state: a position on a staircase, a house on a street, a prefix of a string. dp[i] stores the best value, or the number of ways, for that prefix or position.

Climbing stairs asks how many ways you can climb n steps taking 1 or 2 at a time. The last move came from step n-1 or from step n-2, so ways(n) = ways(n-1) + ways(n-2), which is the Fibonacci recurrence.

House robber asks for the largest sum of house values you can take without taking two adjacent houses. At house i you either skip it (keep dp[i-1]) or rob it (dp[i-2] + value), so dp[i] = max(dp[i-1], dp[i-2] + nums[i]).

Why interviewers ask

These are the standard entry point to DP in interviews. Interviewers use them to check that you can state the 'last decision' that splits the problem, write the recurrence and base cases, and then reduce memory to O(1). Follow-ups such as house robber on a circle check that you can adapt the state.

How it works

  1. 1Ask what the last step or last decision is: the final stair move, whether you rob the last house, how the last character is decoded.
  2. 2Express dp[i] using smaller indices based on that decision: sum the options when counting ways, take max or min when optimising.
  3. 3Set base cases for the smallest indices, such as ways(0) = 1 (one way to stand still) and ways(1) = 1, or robbing nothing gives 0.
  4. 4Loop i from small to large, so dp[i-1] and dp[i-2] are ready when needed.
  5. 5Because only the last two values are read, keep two variables instead of an array: O(n) time and O(1) space.

Worked example · House robber on [2, 7, 9, 3, 1]

  1. prev2 is the best up to house i-2, prev1 the best up to house i-1. Start with prev2 = 0, prev1 = 0.
  2. House 0 (2): cur = max(0, 0 + 2) = 2. Now prev2 = 0, prev1 = 2.
  3. House 1 (7): cur = max(2, 0 + 7) = 7. Rob house 1 instead of house 0. prev2 = 2, prev1 = 7.
  4. House 2 (9): cur = max(7, 2 + 9) = 11. Rob houses 0 and 2. prev2 = 7, prev1 = 11.
  5. House 3 (3): cur = max(11, 7 + 3) = 11. Skipping is better. prev2 = 11, prev1 = 11.
  6. House 4 (1): cur = max(11, 11 + 1) = 12. Answer 12 from houses 0, 2 and 4 (2 + 9 + 1).
  7. Climbing stairs for n = 4 by the same idea: ways = 1, 1, 2, 3, 5, so there are 5 ways (1111, 112, 121, 211, 22).

Picture it

Walking down a street deciding at each house whether to knock, you only need to remember two numbers: your best total if you stopped at the previous house and your best total if you stopped two houses back. Everything older is already summarised in those two numbers.

Reference code

def climb_stairs(n):
    """Ways to climb n steps taking 1 or 2 at a time."""
    one_back, two_back = 1, 1          # ways(0) = ways(1) = 1
    for _ in range(2, n + 1):
        one_back, two_back = one_back + two_back, one_back
    return one_back


def rob(nums):
    """House robber: max sum with no two adjacent houses."""
    prev2, prev1 = 0, 0                # best up to i-2, best up to i-1
    for x in nums:
        cur = max(prev1, prev2 + x)    # skip house i, or rob it
        prev2, prev1 = prev1, cur
    return prev1

# climb_stairs(4) -> 5
# rob([2, 7, 9, 3, 1]) -> 12  (2 + 9 + 1)

Both functions read only the previous two values, so they use two variables: O(n) time and O(1) space. climb_stairs counts ways (sum), rob optimises (max).

Key patterns

Counting ways (stairs, decode ways)

When: How many ways to reach position n

How: dp[i] = sum of dp[i - step] over allowed steps; base dp[0] = 1.

Take or skip (house robber)

When: Pick a subset with no two neighbours, maximise the total

How: dp[i] = max(dp[i-1], dp[i-2] + nums[i]). For a circle, solve twice: without the first house and without the last.

Best ending here (Kadane)

When: Maximum subarray sum

How: cur = max(x, cur + x), best = max(best, cur); dp means 'best subarray ending at i'.

Prefix reachability (word break)

When: Can the prefix of length i be built from pieces?

How: dp[i] = any(dp[j] and s[j:i] in words) over j < i, often limited by the longest word length.

Real-world use

Text segmentation

Word break style DP (dp[i] = can the first i characters be split into dictionary words) is how languages written without spaces, such as Chinese or Japanese, are segmented in classic dictionary-based approaches, choosing the best split by score.

Stock and trading analysis

Maximum subarray (Kadane's algorithm, a 1D DP) finds the best buy-and-hold period or the most profitable stretch of daily returns in analytics dashboards.

Line breaking in typesetting

TeX's Knuth-Plass algorithm chooses line breaks for a paragraph with DP over break positions, minimising total badness instead of filling lines greedily.

Scheduling with cooldowns

Selecting non-adjacent or non-conflicting days or slots for maximum value (for example promotions that cannot run on consecutive days) is house robber with a different label.

In a project

A retail analytics team wants to choose which weeks of the year to run a flash sale for maximum projected revenue, with the rule that sales cannot run in two consecutive weeks to avoid training customers to wait.

  1. Forecast the extra revenue for each of the 52 weeks from past data.
  2. Model it as house robber: best[i] = max(best[i-1], best[i-2] + revenue[i]).
  3. Keep a choice array so you can walk back and list the chosen weeks, not just the total.
  4. If the business adds 'at least two weeks between sales', change the recurrence to best[i-3] + revenue[i] and rerun.

Impact: The plan is provably optimal under the rule, runs instantly, and makes it easy to quantify how much revenue a stricter spacing rule costs, which helps the business decide the policy.

Trade-offs

Use it when

  • The state is one index (position or prefix length)
  • Each answer depends on a fixed number of earlier answers
  • You need counts of ways or a best total along a sequence

Avoid it when

  • The state needs two indices, such as two strings or a capacity (use 2D DP)
  • Choices depend on items far back in arbitrary ways that one index cannot capture
  • A simple greedy is provably correct (for example jump game reachability)

Cost: O(n) time; O(n) space with an array, O(1) with rolling variables. Word break is O(n x L) with L the maximum word length (plus substring costs).

Common mistakes

  • Wrong base cases, such as ways(0) = 0 for climbing stairs, which makes everything zero.
  • Greedily robbing the larger of each adjacent pair instead of using the recurrence.
  • For house robber on a circle, forgetting that the first and last houses are adjacent.
  • Updating rolling variables in the wrong order and overwriting a value before it is used.
  • Not handling an empty array or n = 0.

Interview questions

Quick revision

  • Ask: what is the last decision?
  • Stairs: dp[i] = dp[i-1] + dp[i-2], dp[0] = dp[1] = 1.
  • Robber: dp[i] = max(dp[i-1], dp[i-2] + x).
  • Circular robber: max(rob(nums[1:]), rob(nums[:-1])).
  • Kadane: cur = max(x, cur + x).
  • Only last two values needed: O(1) space.
  • rob([2,7,9,3,1]) = 12; climb_stairs(4) = 5.

Check yourself

1. How many ways are there to climb 5 stairs with steps of 1 or 2?

2. What is the house robber answer for [2, 1, 1, 2]?

3. Which recurrence is house robber?

4. Houses form a circle. What is the standard fix?

Saved on this device. Create a free account to keep it on every device.

Practise this with Sam

A real mock interview, out loud, with an honest scorecard. Free to start.

Start free