1-D Dynamic Programming

Climbing Stairs, House Robber, and Decode Ways With Rolling-Variable O(1) Space

1-D Dynamic Programming

Three classic 1-D DP problems — climbing stairs, house robber, and decode ways — show the full pattern from recurrence to rolling-variable space optimization.

5 min read Level 3/5 #dsa#dp#1d-dp
What you'll learn
  • Write recurrences for climbing stairs, house robber, and decode ways
  • Implement each with a dp array and then compress to O(1) space with rolling variables
  • Recognize when a 1-D recurrence depends only on the previous one or two values

Many DP problems need only a single array indexed by one variable. Once the recurrence is identified, filling the array is mechanical — and when dp[i] depends only on the previous one or two values, the entire array collapses to a pair of variables.

Climbing Stairs

Problem: An n-step staircase; each step you climb 1 or 2 steps. How many distinct ways reach the top?

State: dp[i] = number of ways to reach step i. Recurrence: dp[i] = dp[i-1] + dp[i-2] Base cases: dp[0] = 1, dp[1] = 1

function climbStairs(n) {
  let prev2 = 1, prev1 = 1;
  for (let i = 2; i <= n; i++) {
    const curr = prev1 + prev2;
    prev2 = prev1;
    prev1 = curr;
  }
  return prev1;
}

console.log(climbStairs(10)); // 89

Time O(n), space O(1) — the Fibonacci recurrence with different base cases.

House Robber

Problem: An array of house values; you cannot rob two adjacent houses. Maximize total value stolen.

State: dp[i] = max value robbing from houses 0..i. Recurrence: dp[i] = max(dp[i-1], dp[i-2] + nums[i]) Base cases: dp[0] = nums[0], dp[1] = max(nums[0], nums[1])

function rob(nums) {
  if (nums.length === 0) return 0;
  if (nums.length === 1) return nums[0];
  let prev2 = nums[0];
  let prev1 = Math.max(nums[0], nums[1]);
  for (let i = 2; i < nums.length; i++) {
    const curr = Math.max(prev1, prev2 + nums[i]);
    prev2 = prev1;
    prev1 = curr;
  }
  return prev1;
}

console.log(rob([2, 7, 9, 3, 1])); // 12  (rob houses 0, 2, 4)

Decode Ways

Problem: A string of digits encodes letters where ‘A’=1 … ‘Z’=26. Count distinct decodings.

State: dp[i] = number of ways to decode the first i characters. Recurrence:

  • If s[i-1] is not ‘0’: dp[i] += dp[i-1]
  • If the two-digit number formed by s[i-2]s[i-1] is between 10 and 26: dp[i] += dp[i-2] Base cases: dp[0] = 1 (empty string), dp[1] = s[0] !== '0' ? 1 : 0
function numDecodings(s) {
  const n = s.length;
  let prev2 = 1;
  let prev1 = s[0] !== '0' ? 1 : 0;
  for (let i = 2; i <= n; i++) {
    let curr = 0;
    const oneDigit = Number(s[i - 1]);
    const twoDigit = Number(s.slice(i - 2, i));
    if (oneDigit >= 1) curr += prev1;
    if (twoDigit >= 10 && twoDigit <= 26) curr += prev2;
    prev2 = prev1;
    prev1 = curr;
  }
  return prev1;
}

console.log(numDecodings('226')); // 3 — (2,2,6), (22,6), (2,26)

Pattern Summary

ProblemDepends onRolling vars needed
Climbing stairsi-1, i-22
House robberi-1, i-22
Decode waysi-1, i-22
Fibonaccii-1, i-22

When the recurrence only looks back a fixed number of positions, you can always trade the full dp array for that many scalar variables.

Up Next

Move to two-dimensional DP where the state tracks both a row and a column position, and see how row-rolling keeps space linear.

2-D Dynamic Programming →