Skip to content

Feature/ctci recursive staircase #428

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# [Recursion: Davis' Staircase](https://www.hackerrank.com/challenges/ctci-recursive-staircase)

Find the number of ways to get from the bottom of a staircase
to the top if you can jump 1, 2, or 3 stairs at a time.

- Difficulty: `#medium`
- Category: `#ProblemSolvingIntermediate`

## Failed solution

This solution correctly calculates the result. The problem is its performance,
since due to the traversal of the recursion tree,
it is eventually easy to reach repeated cases that are recalculated each time.

```typescript
def step_perms_compute(n: number): number
if (n == 0) {
return 0
}
if (n == 1) {
return 1
}
if (n == 2) {
return 2
}
if (n == 3) {
return 4
}

return
step_perms_compute(n - 3) +
step_perms_compute(n - 2) +
step_perms_compute(n - 1)
```

## Alternative solution

The final solution introduces a simple caching mechanism,
so that repeated cases are not recalculated.

The trade-off is that the algorithm now requires
more memory to run in less time.

## Generalized solution

In order to comply with some clean code best practices,
I noticed that the step limit in the algorithm is a hard-coded number,
so to comply with the "no magic numbers" rule,
I was forced to find a more generalized solution.

Then I found the following pattern:

- First cases are:

$$ \begin{matrix}
\text{stepPerms(0)} = 0 \\
\text{stepPerms(1)} = 1 \\
\text{stepPerms(2)} = 2 \\
\end{matrix}
$$

- Next step combinations above 2 and less than the step limit are:

$$ \text{stepPerms(number of steps)} = 2^\text{number of steps} + 1 $$

- When `number of steps` are above the limit, the pattern is
the sum of latest `number of steps` previous calls of
`stepPerms(x)` results as follows:

$$ \displaystyle\sum_{
i=\text{number of steps} - \text{limit}}
^\text{number of steps}
stepPerms(\text{number of steps} - i)
$$
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# [Recursion: Davis' Staircase](https://www.hackerrank.com/challenges/ctci-recursive-staircase)

Find the number of ways to get from the bottom of a staircase
to the top if you can jump 1, 2, or 3 stairs at a time.

- Difficulty: `#medium`
- Category: `#ProblemSolvingIntermediate`

Davis has a number of staircases in his house and he likes to
climb each staircase `1`, `2`, or `3` steps at a time.
Being a very precocious child, he wonders how many ways there
are to reach the top of the staircase.

Given the respective heights for each of the staircases in his house,
find and print the number of ways he can climb each staircase,
module $10^10 + 7 $ on a new line.

## Example

`n = 5`

The staircase has `5` steps. Davis can step on the following sequences of steps:

```text
1 1 1 1 1
1 1 1 2
1 1 2 1
1 2 1 1
2 1 1 1
1 2 2
2 2 1
2 1 2
1 1 3
1 3 1
3 1 1
2 3
3 2
```

There are `13` possible ways he can take these `5` steps and `13 modulo 10000000007`

## Function Description

Complete the stepPerms function using recursion in the editor below.

stepPerms has the following parameter(s):

- int n: the number of stairs in the staircase

## Returns

int: the number of ways Davis can climb the staircase, modulo 10000000007

## Input Format

The first line contains a single integer, `s`, the number of staircases in his house.
Each of the following `s` lines contains a single integer,
`n`, the height of staircase `i`.

## Constraints

- $ 1 \leq s \leq 5 $
- $ 1 \leq n \leq 36 $

## Subtasks

- 1 \leq n \leq 20 for `50%` of the maximum score.

## Sample Input

```text
STDIN Function
----- --------
3 s = 3 (number of staircases)
1 first staircase n = 1
3 second n = 3
7 third n = 7
```

## Sample Output

```text
1
4
44
```

## Explanation

Let's calculate the number of ways of climbing
the first two of the Davis' `s = 3` staircases:

1. The first staircase only has `n = 1` step,
so there is only one way for him to
climb it (i.e., by jumping `1` step). Thus, we print `1` on a new line.

2. The second staircase has `n = 3` steps and he can climb it in any of the
four following ways:

1. 1 -> 1 -> 1
2. 1 -> 2
3. 2 -> 1
4. 3

Thus, we print `4` on a new line.
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { describe, expect, it } from '@jest/globals';
import { logger as console } from '../../../logger';

import {
stepPerms,
step_perms_comput_with_cache
} from './ctci_recursive_staircase';
import TEST_CASES from './ctci_recursive_staircase.testcases.json';
import TEST_CASES_GENERALIZED from './ctci_recursive_staircase_generalized.testcases.json';

describe('ctci_recursive_staircase', () => {
it('stepPerms test cases', () => {
expect.assertions(8);

TEST_CASES.forEach((testSet) => {
testSet?.tests.forEach((test) => {
const answer = stepPerms(test.input);

console.debug(`stepPerms(${test.input}) solution found: ${answer}`);

expect(answer).toStrictEqual(test.expected);
});
});
});

it('step_perms_comput_with_cache test cases', () => {
expect.assertions(3);

TEST_CASES_GENERALIZED.forEach((testSet) => {
testSet?.tests.forEach((test) => {
const initial_cache: Record<number, number> = {};
const answer = step_perms_comput_with_cache(
test.input,
initial_cache,
test.limit
);

console.debug(
`step_perms_comput_with_cache(${test.input}, ${initial_cache}, ${test.limit}) solution found: ${answer}`
);

expect(answer).toStrictEqual(test.expected);
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
[
{
"title": "Sample Test case 0",
"tests": [
{
"input": 1,
"expected": 1
},
{
"input": 3,
"expected": 4
},
{
"input": 7,
"expected": 44
}
]
},
{
"title": "Sample Test case 9",
"tests": [
{
"input": 5,
"expected": 13
},
{
"input": 8,
"expected": 81
}
]
},
{
"title": "Sample Test case 10",
"tests": [
{
"input": 15,
"expected": 5768
},
{
"input": 20,
"expected": 121415
},
{
"input": 27,
"expected": 8646064
}
]
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* @link Problem definition [[docs/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci-recursive-staircase.md]]
* @see Solution Notes: [[docs/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci-recursive-staircase-solution-notes.md]]
*/

const TOP_LIMIT = 10 ** 10 + 7;
const STEPS_LIMIT = 3;

export function step_perms_comput_with_cache(
n_steps: number,
cache: Record<number, number>,
steps_limit: number
): number {
if (0 <= n_steps && n_steps <= 2) {
return n_steps;
}

const keys = new Set(Object.values(cache));
let result = 0;

for (let i = 1; i <= Math.min(steps_limit, n_steps); i++) {
const searchKey = n_steps - i;
if (!keys.has(searchKey)) {
cache[searchKey] = step_perms_comput_with_cache(
searchKey,
cache,
steps_limit
);
}

result += cache[searchKey];
}

return result + (n_steps <= steps_limit ? 1 : 0);
}

export function stepPerms(n: number): number {
const initial_cache: Record<number, number> = {};
return (
step_perms_comput_with_cache(n, initial_cache, STEPS_LIMIT) % TOP_LIMIT
);
}

export default { stepPerms, step_perms_comput_with_cache };
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[
{
"title": "Own sample 1",
"tests": [
{
"input": 4,
"limit": 3,
"expected": 7
}
]
},
{
"title": "Own sample 2",
"tests": [
{
"input": 5,
"limit": 4,
"expected": 15
}
]
},
{
"title": "Own sample 3",
"tests": [
{
"input": 6,
"limit": 2,
"expected": 13
}
]
}
]