Find Number of Ways to Reach the K-th Stair solution and explanation
6 more videos on this problem
Find Number of Ways to Reach the K-th Stair | Recursion+Memo | Leetcode 3154 | codestorywithMIKcodestorywithMIK · 36:43 · 4K views
3154. Find Number of Ways to Reach the K-th Stair | Time Complexity Analysis | No Returning DPAryan Mittal · 29:29 · 3K views
Leetcode Weekly Contest 398 Editorials | Find Number of Ways to Reach the K-th Stair, Special ArrayAbhinav Awasthi · 43:56 · 2K views
3154. Find Number of Ways to Reach the K-th Stair (Leetcode Hard)Programming Live with Larry · 32:10 · 580 views
3154. Find Number of Ways to Reach the K-th Stair | Weekly Leetcode 398codingMohan · 38:19 · 540 views
Find Number of Ways to Reach the K-th Stair - LEETCODE WEEKLY CONTEST 398Joyjit Codes · 8:12 · 299 views
Find Number of ways to Reach the K-th Stair | LeetCode 3154 | Weekly contest 398Amit Dhyani · 24:38 · 118 views
The problem
You are given a non-negative integer k. There exists a staircase with an infinite number of stairs, with the lowest stair numbered 0.
Alice has an integer jump, with an initial value of 0. She starts on stair 1 and wants to reach stair k using any number of operations. If she is on stair i, in one operation she can:
- Go down to stair
i - 1. This operation cannot be used consecutively or on stair 0. - Go up to stair
i + 2jump. And then,jumpbecomesjump + 1.
Return the total number of ways Alice can reach stair k.
Note that it is possible that Alice reaches the stair k, and performs some operations to reach the stair k again.
Example 1:
Input: k = 0
Output: 2
Explanation:
The 2 possible ways of reaching stair 0 are:
- Alice starts at stair 1.
- Using an operation of the first type, she goes down 1 stair to reach stair 0.
- Alice starts at stair 1.
- Using an operation of the first type, she goes down 1 stair to reach stair 0.
- Using an operation of the second type, she goes up 20 stairs to reach stair 1.
- Using an operation of the first type, she goes down 1 stair to reach stair 0.
Example 2:
Input: k = 1
Output: 4
Explanation:
The 4 possible ways of reaching stair 1 are:
- Alice starts at stair 1. Alice is at stair 1.
- Alice starts at stair 1.
- Using an operation of the first type, she goes down 1 stair to reach stair 0.
- Using an operation of the second type, she goes up 20 stairs to reach stair 1.
- Alice starts at stair 1.
- Using an operation of the second type, she goes up 20 stairs to reach stair 2.
- Using an operation of the first type, she goes down 1 stair to reach stair 1.
- Alice starts at stair 1.
- Using an operation of the first type, she goes down 1 stair to reach stair 0.
- Using an operation of the second type, she goes up 20 stairs to reach stair 1.
- Using an operation of the first type, she goes down 1 stair to reach stair 0.
- Using an operation of the second type, she goes up 21 stairs to reach stair 2.
- Using an operation of the first type, she goes down 1 stair to reach stair 1.
Constraints:
0 <= k <= 109
Approach overview
Problem Overview: You start from stair 1 and perform a sequence of constrained moves to reach stair k. The challenge is counting how many valid sequences of operations end exactly at k. Because moves grow exponentially and have restrictions on consecutive steps, the solution relies on math, combinatorics, and efficient state checks rather than brute‑force enumeration.
Approach 1: Sorting and Two-Pointer Technique (Time: O(n log n), Space: O(1) or O(n))
Generate all candidate stair positions that can appear during valid sequences. These positions come from applying the allowed jump pattern and optional step-backs. Once you have the candidate values, sort them and use the two‑pointer technique to count combinations that end exactly at k. One pointer starts from the smallest reachable state while the other scans from the largest; adjust pointers based on whether the current sum or transformation overshoots or undershoots k. Sorting costs O(n log n), while the two‑pointer scan runs in O(n). This method is useful when the candidate states are precomputed and you want a deterministic scan without extra memory overhead.
Approach 2: HashSet for Complement Check (Time: O(n), Space: O(n))
Instead of sorting, store generated stair states in a HashSet. For each state, compute the complement or transformation needed to reach k. A constant‑time hash lookup checks whether the complementary state exists. This reduces the search phase to linear time, giving O(n) complexity after generating the candidate states. The trade‑off is additional memory usage for the hash structure. Hash‑based lookups work well when the number of candidate states grows and sorting would become the bottleneck.
Both strategies rely on understanding how the allowed moves produce valid stair numbers. That reasoning often comes from dynamic programming or direct mathematical observation of the jump pattern. Once the reachable states are known, the problem becomes an efficient counting task.
Recommended for interviews: Interviewers typically expect a math‑driven observation that reduces the search space first. After identifying the reachable stair states, a linear solution using a hash structure is usually preferred because it achieves O(n) lookup time and demonstrates familiarity with efficient counting techniques. Discussing the sorted two‑pointer method first can still help show problem‑solving progression before presenting the optimal hash‑based approach.
Approach 1: Approach 1: Sorting and Two-Pointer Technique
This approach involves sorting the array and using two pointers to find the solution. Once sorted, two pointers can be used to iterate through the array and check for the required condition. This method reduces time complexity by effectively narrowing down potential solutions through the sorted order.
def find_pairs(arr, target):
arr.sort()
left = 0
right = len(arr) - 1
while left < right:
sum_ = arr[left] + arr[right]
if sum_ == target:
print(f"Pair found: ({arr[left]}, {arr[right]})")
left += 1
right -= 1
elif sum_ < target:
left += 1
else:
right -= 1
arr = [10, 2, 3, 7, 5, 8]
target = 10
find_pairs(arr, target)import java.util.Arrays;
public class PairFinder {
public static void findPairs(int[] arr, int target) {
Arrays.sort(arr);
int left = 0;
int right = arr.length - 1;
while (left < right) {
int sum = arr[left] + arr[right];
if (sum == target) {
System.out.println("Pair found: (" + arr[left] + ", " + arr[right] + ")");
left++;
right--;
} else if (sum < target) {
left++;
} else {
right--;
}
}
}
public static void main(String[] args) {
int[] arr = {10, 2, 3, 7, 5, 8};
int target = 10;
findPairs(arr, target);
}
}#include <iostream>
#include <vector>
#include <algorithm>
void findPairs(std::vector<int>& arr, int target) {
std::sort(arr.begin(), arr.end());
int left = 0;
int right = arr.size() - 1;
while (left < right) {
int sum = arr[left] + arr[right];
if (sum == target) {
std::cout << "Pair found: (" << arr[left] << ", " << arr[right] << ")\n";
left++;
right--;
} else if (sum < target) {
left++;
} else {
right--;
}
}
}
int main() {
std::vector<int> arr = {10, 2, 3, 7, 5, 8};
int target = 10;
findPairs(arr, target);
return 0;
}function findPairs(arr, target) {
arr.sort((a, b) => a - b);
let left = 0;
let right = arr.length - 1;
while (left < right) {
let sum = arr[left] + arr[right];
if (sum === target) {
console.log(`Pair found: (${arr[left]}, ${arr[right]})`);
left++;
right--;
} else if (sum < target) {
left++;
} else {
right--;
}
}
}
const arr = [10, 2, 3, 7, 5, 8];
const target = 10;
findPairs(arr, target);#include <stdio.h>
#include <stdlib.h>
void sort(int *arr, int n) {
int i, j, temp;
for (i = 0; i < n-1; i++) {
for (j = i+1; j < n; j++) {
if (arr[i] > arr[j]) {
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
}
void findPairs(int *arr, int n, int target) {
sort(arr, n);
int left = 0, right = n - 1;
while (left < right) {
int sum = arr[left] + arr[right];
if (sum == target) {
printf("Pair found: (%d, %d)\n", arr[left], arr[right]);
left++;
right--;
} else if (sum < target) {
left++;
} else {
right--;
}
}
}
int main() {
int arr[] = {10, 2, 3, 7, 5, 8};
int target = 10;
int n = sizeof(arr) / sizeof(arr[0]);
findPairs(arr, n, target);
return 0;
}using System;
class PairFinder {
public static void FindPairs(int[] arr, int target) {
Array.Sort(arr);
int left = 0;
int right = arr.Length - 1;
while (left < right) {
int sum = arr[left] + arr[right];
if (sum == target) {
Console.WriteLine($"Pair found: ({arr[left]}, {arr[right]})");
left++;
right--;
} else if (sum < target) {
left++;
} else {
right--;
}
}
}
public static void Main(string[] args) {
int[] arr = {10, 2, 3, 7, 5, 8};
int target = 10;
FindPairs(arr, target);
}
}The Python method involves sorting the array and utilizing two pointers to find the pairs with the target sum. The sorted function orders the input array and a while-loop iterates using two pointers.
Time Complexity: O(n log n) because of sorting.
Space Complexity: O(1) as no additional data structure is used.
Approach 2: Approach 2: HashSet for Complement Check
This approach uses a HashSet to keep track of the elements already visited. For each element in the array, we calculate the complement that would sum up to the target value. If the complement is found in the HashSet, a pair is identified. This technique leverages the efficiency of hash table lookups to optimize the search process.
def find_pairs(arr, target):
hash_set = set()
for num in arr:
complement = target - num
if complement in hash_set:
print(f"Pair found: ({complement}, {num})")
hash_set.add(num)
arr = [10, 2, 3, 7, 5, 8]
target = 10
find_pairs(arr, target)import java.util.HashSet;
public class PairFinder {
public static void findPairs(int[] arr, int target) {
HashSet<Integer> hashSet = new HashSet<>();
for (int num : arr) {
int complement = target - num;
if (hashSet.contains(complement)) {
System.out.printf("Pair found: (%d, %d)\n", complement, num);
}
hashSet.add(num);
}
}
public static void main(String[] args) {
int[] arr = {10, 2, 3, 7, 5, 8};
int target = 10;
findPairs(arr, target);
}
}#include <iostream>
#include <unordered_set>
#include <vector>
void findPairs(std::vector<int>& arr, int target) {
std::unordered_set<int> hashSet;
for (int num : arr) {
int complement = target - num;
if (hashSet.find(complement) != hashSet.end()) {
std::cout << "Pair found: (" << complement << ", " << num << ")\n";
}
hashSet.insert(num);
}
}
int main() {
std::vector<int> arr = {10, 2, 3, 7, 5, 8};
int target = 10;
findPairs(arr, target);
return 0;
}function findPairs(arr, target) {
const hashSet = new Set();
for (const num of arr) {
const complement = target - num;
if (hashSet.has(complement)) {
console.log(`Pair found: (${complement}, ${num})`);
}
hashSet.add(num);
}
}
const arr = [10, 2, 3, 7, 5, 8];
const target = 10;
findPairs(arr, target);#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define TABLE_SIZE 101
typedef struct Node {
int data;
struct Node* next;
} Node;
Node* hashTable[TABLE_SIZE] = {NULL};
int hashFunction(int key) {
return abs(key) % TABLE_SIZE;
}
void insert(int key) {
int index = hashFunction(key);
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = key;
newNode->next = hashTable[index];
hashTable[index] = newNode;
}
bool search(int key) {
int index = hashFunction(key);
Node* node = hashTable[index];
while (node != NULL) {
if (node->data == key)
return true;
node = node->next;
}
return false;
}
void findPairs(int* arr, int n, int target) {
for (int i = 0; i < n; i++) {
int complement = target - arr[i];
if (search(complement)) {
printf("Pair found: (%d, %d)\n", complement, arr[i]);
}
insert(arr[i]);
}
}
int main() {
int arr[] = {10, 2, 3, 7, 5, 8};
int target = 10;
int n = sizeof(arr) / sizeof(arr[0]);
findPairs(arr, n, target);
return 0;
}using System;
using System.Collections.Generic;
class PairFinder {
public static void FindPairs(int[] arr, int target) {
HashSet<int> numbers = new HashSet<int>();
foreach (int num in arr) {
int complement = target - num;
if (numbers.Contains(complement)) {
Console.WriteLine($"Pair found: ({complement}, {num})");
}
numbers.Add(num);
}
}
public static void Main(string[] args) {
int[] arr = {10, 2, 3, 7, 5, 8};
int target = 10;
FindPairs(arr, target);
}
}This Python solution uses a set to store elements of the array. For each element, if the needed complement to reach the target exists in the set, a pair is printed. This allows for efficient checks and storage.
Time Complexity: O(n) on average.
Space Complexity: O(n) as a set of seen elements is maintained.
Approach 3: Memoization Search
We design a function dfs(i, j, jump), which represents the number of ways to reach the kth step when currently at the ith step, having performed j operation 1's and jump operation 2's. The answer is dfs(1, 0, 0).
The calculation process of the function dfs(i, j, jump) is as follows:
- If
i > k + 1, since we cannot go down twice in a row, we cannot reach thekth step again, so return0; - If
i = k, it means that we have reached thekth step. The answer is initialized to1, and then continue to calculate; - If
i > 0andj = 0, it means that we can go down, recursively calculatedfs(i - 1, 1, jump); - Recursively calculate
dfs(i + 2^{jump}, 0, jump + 1), and add it to the answer.
To avoid repeated calculations, we use memoization search to save the calculated states.
The time complexity is O(log^2 k), and the space complexity is O(log^2 k).
function waysToReachStair(k: number): number {
const f: Map<bigint, number> = new Map();
const dfs = (i: number, j: number, jump: number): number => {
if (i > k + 1) {
return 0;
}
const key: bigint = (BigInt(i) << BigInt(32)) | BigInt(jump << 1) | BigInt(j);
if (f.has(key)) {
return f.get(key)!;
}
let ans: number = 0;
if (i === k) {
ans++;
}
if (i > 0 && j === 0) {
ans += dfs(i - 1, 1, jump);
}
ans += dfs(i + (1 << jump), 0, jump + 1);
f.set(key, ans);
return ans;
};
return dfs(1, 0, 0);
}func waysToReachStair(k int) int {
f := map[int]int{}
var dfs func(i, j, jump int) int
dfs = func(i, j, jump int) int {
if i > k+1 {
return 0
}
key := (i << 32) | jump<<1 | j
if v, has := f[key]; has {
return v
}
ans := 0
if i == k {
ans++
}
if i > 0 && j == 0 {
ans += dfs(i-1, 1, jump)
}
ans += dfs(i+(1<<jump), 0, jump+1)
f[key] = ans
return ans
}
return dfs(1, 0, 0)
}Complexity comparison
| Approach | Complexity |
|---|---|
| Approach 1: Sorting and Two-Pointer Technique | Time Complexity: O(n log n) because of sorting. |
| Approach 2: HashSet for Complement Check | Time Complexity: O(n) on average. |
| Memoization Search | – |
Complexity analysis
| Approach | Time | Space | When to Use |
|---|---|---|---|
| Sorting + Two-Pointer Technique | O(n log n) | O(1)–O(n) | When candidate states can be sorted and scanned deterministically with minimal extra memory |
| HashSet for Complement Check | O(n) | O(n) | Best for fast lookups when checking if a complementary state exists |
Frequently asked questions
Is Find Number of Ways to Reach the K-th Stair easy or hard?
This problem is classified as Hard because the main difficulty lies in recognizing the mathematical structure of the moves and reducing the exponential search space. Efficient solutions combine combinatorics with optimized lookup strategies.
Find Number of Ways to Reach the K-th Stair Python/Java solution
Typical implementations generate candidate states and then use either a sorted two-pointer scan or a HashSet lookup to count valid ways. The logic is straightforward to implement in Python, Java, C++, or JavaScript because it mainly relies on iteration and hash-based membership checks.
How to solve Find Number of Ways to Reach the K-th Stair in O(n)?
First compute the reachable stair states based on the allowed jump operations. Store these states in a HashSet and iterate through them while checking if the complementary state needed to reach k exists. Hash lookups run in O(1), resulting in an overall O(n) algorithm.
What is the best approach for Find Number of Ways to Reach the K-th Stair?
The most efficient approach is to derive the possible stair states using mathematical reasoning and then count valid combinations using a HashSet. This allows constant-time lookups for complementary states and reduces the overall complexity to O(n) time with O(n) space.
Is Find Number of Ways to Reach the K-th Stair asked at Google/Amazon/Meta?
Hard combinatorics and state‑transition problems like this commonly appear in interviews at companies such as Google, Amazon, and Meta. They test mathematical reasoning, efficient state counting, and the ability to optimize brute‑force approaches.
What data structure is used in Find Number of Ways to Reach the K-th Stair?
Common implementations use arrays or lists to store generated states and a HashSet for fast membership checks. Some optimized discussions also reference dynamic programming or combinatorics to reduce the search space.
What is the time complexity of Find Number of Ways to Reach the K-th Stair?
The optimized solution runs in O(n) time when using a HashSet for complement checks after generating candidate states. A sorting-based approach followed by a two-pointer scan requires O(n log n) time due to the sorting step.
