Skip to content

Commit 5be7bbe

Browse files
authored
Merge pull request #499 from sir-gon/feature/ctci-recursive-staircase
Feature/ctci recursive staircase
2 parents c2ed06d + 0fb9094 commit 5be7bbe

File tree

6 files changed

+350
-0
lines changed

6 files changed

+350
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# [Recursion: Davis' Staircase](https://www.hackerrank.com/challenges/ctci-recursive-staircase)
2+
3+
Find the number of ways to get from the bottom of a staircase
4+
to the top if you can jump 1, 2, or 3 stairs at a time.
5+
6+
- Difficulty: `#medium`
7+
- Category: `#ProblemSolvingIntermediate`
8+
9+
## Failed solution
10+
11+
This solution correctly calculates the result. The problem is its performance,
12+
since due to the traversal of the recursion tree,
13+
it is eventually easy to reach repeated cases that are recalculated each time.
14+
15+
```typescript
16+
def step_perms_compute(n: number): number
17+
if (n == 0) {
18+
return 0
19+
}
20+
if (n == 1) {
21+
return 1
22+
}
23+
if (n == 2) {
24+
return 2
25+
}
26+
if (n == 3) {
27+
return 4
28+
}
29+
30+
return
31+
step_perms_compute(n - 3) +
32+
step_perms_compute(n - 2) +
33+
step_perms_compute(n - 1)
34+
```
35+
36+
## Alternative solution
37+
38+
The final solution introduces a simple caching mechanism,
39+
so that repeated cases are not recalculated.
40+
41+
The trade-off is that the algorithm now requires
42+
more memory to run in less time.
43+
44+
## Generalized solution
45+
46+
In order to comply with some clean code best practices,
47+
I noticed that the step limit in the algorithm is a hard-coded number,
48+
so to comply with the "no magic numbers" rule,
49+
I was forced to find a more generalized solution.
50+
51+
Then I found the following pattern:
52+
53+
- First cases are:
54+
55+
$$ \begin{matrix}
56+
\text{stepPerms(0)} = 0 \\
57+
\text{stepPerms(1)} = 1 \\
58+
\text{stepPerms(2)} = 2 \\
59+
\end{matrix}
60+
$$
61+
62+
- Next step combinations above 2 and less than the step limit are:
63+
64+
$$ \text{stepPerms(number of steps)} = 2^\text{number of steps} + 1 $$
65+
66+
- When `number of steps` are above the limit, the pattern is
67+
the sum of latest `number of steps` previous calls of
68+
`stepPerms(x)` results as follows:
69+
70+
$$ \displaystyle\sum_{
71+
i=\text{number of steps} - \text{limit}}
72+
^\text{number of steps}
73+
stepPerms(\text{number of steps} - i)
74+
$$
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# [Recursion: Davis' Staircase](https://www.hackerrank.com/challenges/ctci-recursive-staircase)
2+
3+
Find the number of ways to get from the bottom of a staircase
4+
to the top if you can jump 1, 2, or 3 stairs at a time.
5+
6+
- Difficulty: `#medium`
7+
- Category: `#ProblemSolvingIntermediate`
8+
9+
Davis has a number of staircases in his house and he likes to
10+
climb each staircase `1`, `2`, or `3` steps at a time.
11+
Being a very precocious child, he wonders how many ways there
12+
are to reach the top of the staircase.
13+
14+
Given the respective heights for each of the staircases in his house,
15+
find and print the number of ways he can climb each staircase,
16+
module $10^10 + 7 $ on a new line.
17+
18+
## Example
19+
20+
`n = 5`
21+
22+
The staircase has `5` steps. Davis can step on the following sequences of steps:
23+
24+
```text
25+
1 1 1 1 1
26+
1 1 1 2
27+
1 1 2 1
28+
1 2 1 1
29+
2 1 1 1
30+
1 2 2
31+
2 2 1
32+
2 1 2
33+
1 1 3
34+
1 3 1
35+
3 1 1
36+
2 3
37+
3 2
38+
```
39+
40+
There are `13` possible ways he can take these `5` steps and `13 modulo 10000000007`
41+
42+
## Function Description
43+
44+
Complete the stepPerms function using recursion in the editor below.
45+
46+
stepPerms has the following parameter(s):
47+
48+
- int n: the number of stairs in the staircase
49+
50+
## Returns
51+
52+
int: the number of ways Davis can climb the staircase, modulo 10000000007
53+
54+
## Input Format
55+
56+
The first line contains a single integer, `s`, the number of staircases in his house.
57+
Each of the following `s` lines contains a single integer,
58+
`n`, the height of staircase `i`.
59+
60+
## Constraints
61+
62+
- $ 1 \leq s \leq 5 $
63+
- $ 1 \leq n \leq 36 $
64+
65+
## Subtasks
66+
67+
- 1 \leq n \leq 20 for `50%` of the maximum score.
68+
69+
## Sample Input
70+
71+
```text
72+
STDIN Function
73+
----- --------
74+
3 s = 3 (number of staircases)
75+
1 first staircase n = 1
76+
3 second n = 3
77+
7 third n = 7
78+
```
79+
80+
## Sample Output
81+
82+
```text
83+
1
84+
4
85+
44
86+
```
87+
88+
## Explanation
89+
90+
Let's calculate the number of ways of climbing
91+
the first two of the Davis' `s = 3` staircases:
92+
93+
1. The first staircase only has `n = 1` step,
94+
so there is only one way for him to
95+
climb it (i.e., by jumping `1` step). Thus, we print `1` on a new line.
96+
97+
2. The second staircase has `n = 3` steps and he can climb it in any of the
98+
four following ways:
99+
100+
1. 1 -> 1 -> 1
101+
2. 1 -> 2
102+
3. 2 -> 1
103+
4. 3
104+
105+
Thus, we print `4` on a new line.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* @link Problem definition [[docs/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci-recursive-staircase.md]]
3+
* @see Solution Notes: [[docs/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci-recursive-staircase-solution-notes.md]]
4+
*/
5+
6+
const TOP_LIMIT = 10 ** 10 + 7;
7+
const STEPSLIMIT = 3;
8+
9+
export class StepPerms {
10+
TOP_LIMIT = 1;
11+
12+
STEPS_LIMIT = 1;
13+
14+
CACHE = {};
15+
16+
constructor(topLimit, stepsLimit) {
17+
this.TOP_LIMIT = topLimit;
18+
this.STEPS_LIMIT = stepsLimit;
19+
}
20+
21+
stepPermsComputWithCache(nSteps) {
22+
if (nSteps >= 0 && nSteps <= 2) {
23+
return nSteps;
24+
}
25+
26+
const keys = new Set(Object.keys(this.CACHE));
27+
let result = 0;
28+
29+
for (let i = 1; i <= Math.min(this.STEPS_LIMIT, nSteps); i++) {
30+
const searchKey = nSteps - i;
31+
if (!keys.has(searchKey.toString())) {
32+
this.CACHE[searchKey] = this.stepPermsComputWithCache(searchKey);
33+
}
34+
35+
result += this.CACHE[searchKey];
36+
}
37+
38+
return result + (nSteps <= this.STEPS_LIMIT ? 1 : 0);
39+
}
40+
}
41+
42+
export function stepPerms(n) {
43+
const stairs = new StepPerms(TOP_LIMIT, STEPSLIMIT);
44+
45+
return stairs.stepPermsComputWithCache(n) % TOP_LIMIT;
46+
}
47+
48+
export default { stepPerms, StepPerms };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { describe, expect, it } from '@jest/globals';
2+
import { logger as console } from '../../../logger.js';
3+
4+
import { stepPerms, StepPerms } from './ctci_recursive_staircase.js';
5+
import TEST_CASES from './ctci_recursive_staircase.testcases.json';
6+
import TEST_CASES_GENERALIZED from './ctci_recursive_staircase_generalized.testcases.json';
7+
8+
describe('ctci_recursive_staircase', () => {
9+
it('stepPerms test cases', () => {
10+
expect.assertions(8);
11+
12+
TEST_CASES.forEach((testSet) => {
13+
testSet?.tests.forEach((test) => {
14+
const answer = stepPerms(test.n_steps);
15+
16+
console.debug(`stepPerms(${test.n_steps}) solution found: ${answer}`);
17+
18+
expect(answer).toStrictEqual(test.expected);
19+
});
20+
});
21+
});
22+
23+
it('stepPermsComputWithCache test cases', () => {
24+
expect.assertions(3);
25+
26+
const TOP_LIMIT = 10 ** 10 + 7;
27+
28+
TEST_CASES_GENERALIZED.forEach((testSet) => {
29+
testSet?.tests.forEach((test) => {
30+
const stairs = new StepPerms(TOP_LIMIT, test.steps_limit);
31+
32+
const answer = stairs.stepPermsComputWithCache(test.n_steps);
33+
34+
console.debug(
35+
`stepPermsComputWithCache(${test.input}, ${test.limit}) solution found: ${answer}`
36+
);
37+
38+
expect(answer).toStrictEqual(test.expected);
39+
});
40+
});
41+
});
42+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
[
2+
{
3+
"title": "Sample Test case 0",
4+
"tests": [
5+
{
6+
"n_steps": 1,
7+
"expected": 1
8+
},
9+
{
10+
"n_steps": 3,
11+
"expected": 4
12+
},
13+
{
14+
"n_steps": 7,
15+
"expected": 44
16+
}
17+
]
18+
},
19+
{
20+
"title": "Sample Test case 9",
21+
"tests": [
22+
{
23+
"n_steps": 5,
24+
"expected": 13
25+
},
26+
{
27+
"n_steps": 8,
28+
"expected": 81
29+
}
30+
]
31+
},
32+
{
33+
"title": "Sample Test case 10",
34+
"tests": [
35+
{
36+
"n_steps": 15,
37+
"expected": 5768
38+
},
39+
{
40+
"n_steps": 20,
41+
"expected": 121415
42+
},
43+
{
44+
"n_steps": 27,
45+
"expected": 8646064
46+
}
47+
]
48+
}
49+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
[
2+
{
3+
"title": "Own sample 1",
4+
"tests": [
5+
{
6+
"n_steps": 4,
7+
"steps_limit": 3,
8+
"expected": 7
9+
}
10+
]
11+
},
12+
{
13+
"title": "Own sample 2",
14+
"tests": [
15+
{
16+
"n_steps": 5,
17+
"steps_limit": 4,
18+
"expected": 15
19+
}
20+
]
21+
},
22+
{
23+
"title": "Own sample 3",
24+
"tests": [
25+
{
26+
"n_steps": 6,
27+
"steps_limit": 2,
28+
"expected": 13
29+
}
30+
]
31+
}
32+
]

0 commit comments

Comments
 (0)