Number of Ways Up the Stair
Problem
This problem will act as a template to solving dynamic programming problems in our future problems. We will use these steps below to solve almost all of our dynamic programming problems.
Problem Description
You are climbing up a staircase. It takes steps to reach the top.
Each time you can either climb or steps.
In how many distinct ways can you climb to the top?
Given:
0 < n
Testcases
Input: n = 2
Output: 2
There are two ways to climb to the top:
- 1 step + 1 step
- 2 steps
Input: n = 3
Output: 3
There are three ways to climb to the top:
- 1 step + 1 step + 1 step
- 1 step + 2 steps
- 2 steps + 1 step
Solution
Subproblems
At the -th step, there are two ways to get to the -th step:
- Take step from the -th step.
- Take steps from the -th step.
After we found that the -th step depends on the -th and -th step, we need to find the relationship between the -th step and the -th and -th step.
Recurrence Relation
We can see that the number of ways to get to the -th step is the sum of the number of ways to get to the -th and -th step. In more general terms, the problem is the sum of the subproblems.
Let be the number of ways to get to the -th step. We can form the recurrence relation as follows:
Base Cases
We also need to consider the base cases, which are the first two steps.
- There is way to get to the first step, i.e. take step.
- There are ways to get to the second step, i.e. take step twice or take steps.
Solving the Problem
We can solve the problem using the recurrence relation above, which is exactly the same as the Fibonacci sequence, with only differences in the base cases.
So with the bottom-up approach, we can solve the problem similar to the Fibonacci sequence. We will use a memoization array of size , one-based index, because the smallest base case is . Then, we can calculate the next values using the necessary previous values.
function number_of_ways_up_the_stair(n: int) -> int {
// initialize memoization array
let dp = [...] of size n
one-based index
let dp[1] = 1
let dp[2] = 2
// calculate next values
for i from 3 to n {
dp[i] = dp[i - 1] + dp[i - 2]
}
return dp[n]
}
Time complexity:
Space complexity:
The solution to the number of ways up the stair problem with .
Space Optimization
We can find that we only need the previous two values to calculate the next value, so we can optimize the space complexity to by only storing the previous two values.
function number_of_ways_up_the_stair(n: int) -> int {
// initialize base cases
let prev = 1
let curr = 2
// exceptional case for n = 1
if n == 1 {
return prev
}
// calculate next values
for i from 3 to n {
let next = prev + curr
prev = curr
curr = next
}
return curr
}
Time complexity:
Space complexity:
The space optimized solution to the number of ways up the stair problem with .
What is the main goal of forming the recurrence relation in the beginning?
Implementation
- Python
- C++
- Rust
We will implement the solution similar to the Fibonacci sequence problem. However, we will use a memoization array of size instead of , because the problem is one-based index, and we don't want to complicate the code with the offset. We will ignore the first element.
In your own implementation, you can use a memoization array of size and offset the index by .
Note that is exceptional, because initializing the -nd element will cause an index out of bounds error.
def numberOfWaysUpTheStair(n):
# exceptional case for n = 1
if n == 1:
return 1
# initialize memoization list
dp = [0] * (n + 1)
dp[1] = 1
dp[2] = 2
# calculate next values
for i in range(3, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]
def numberOfWaysUpTheStair(n):
# initialize base cases
prev = 1
curr = 2
# exceptional case for n = 1
if n == 1:
return prev
# calculate next values
for i in range(3, n + 1):
next = prev + curr
prev = curr
curr = next
return curr
We will implement the solution similar to the Fibonacci sequence problem. However, we will use a memoization array of size instead of , because the problem is one-based index, and we don't want to complicate the code with the offset. We will ignore the first element.
In your own implementation, you can use a memoization array of size and offset the index by .
Note that is exceptional, because initializing the -nd element will cause an index out of bounds error.
#include <vector>
int number_of_ways_up_the_stair(int n) {
// exceptional case for n = 1
if (n == 1) {
return 1;
}
// initialize memoization vector
std::vector<int> dp(n + 1, 0);
dp[1] = 1;
dp[2] = 2;
// calculate next values
for (int i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
int number_of_ways_up_the_stair(int n) {
// initialize base cases
int prev = 1;
int curr = 2;
// exceptional case for n = 1
if (n == 1) {
return prev;
}
// calculate next values
for (int i = 3; i <= n; i++) {
int next = prev + curr;
prev = curr;
curr = next;
}
return curr;
}
We will implement the solution similar to the Fibonacci sequence problem. However, we will use a memoization array of size instead of , because the problem is one-based index, and we don't want to complicate the code with the offset. We will ignore the first element.
In your own implementation, you can use a memoization array of size and offset the index by .
Note that is exceptional, because initializing the -nd element will cause an index out of bounds error.
fn number_of_ways_up_the_stair(n: usize) -> usize {
// exceptional case for n = 1
if n == 1 {
return 1;
}
// initialize memoization vector
let mut dp = vec![0; n + 1];
dp[1] = 1;
dp[2] = 2;
// calculate next values
for i in 3..=n {
dp[i] = dp[i - 1] + dp[i - 2];
}
dp[n]
}
fn number_of_ways_up_the_stair(n: usize) -> usize {
// initialize base cases
let mut prev = 1;
let mut curr = 2;
// exceptional case for n = 1
if n == 1 {
return prev;
}
// calculate next values
for _ in 3..=n {
let next = prev + curr;
prev = curr;
curr = next;
}
curr
}
Related Topics
LeetCode - Climbing Stairs: https://leetcode.com/problems/climbing-stairs/