Skip to content

Commit 56d346f

Browse files
authored
Merge pull request #554 from sir-gon/feature/ctci-recursive-staircase
Feature/ctci recursive staircase
2 parents db75212 + 920d18d commit 56d346f

File tree

5 files changed

+176
-63
lines changed

5 files changed

+176
-63
lines changed

docs/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci-recursive-staircase-solution-notes.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,35 @@ so that repeated cases are not recalculated.
3636

3737
The trade-off is that the algorithm now requires
3838
more memory to run in less time.
39+
40+
## Generalized solution
41+
42+
In order to comply with some clean code best practices,
43+
I noticed that the step limit in the algorithm is a hard-coded number,
44+
so to comply with the "no magic numbers" rule,
45+
I was forced to find a more generalized solution.
46+
47+
Then I found the following pattern:
48+
49+
- First cases are:
50+
51+
$$ \begin{matrix}
52+
\text{stepPerms(0)} = 0 \\
53+
\text{stepPerms(1)} = 1 \\
54+
\text{stepPerms(2)} = 2 \\
55+
\end{matrix}
56+
$$
57+
58+
- Next step combinations above 2 and less than the step limit are:
59+
60+
$$ \text{stepPerms(number of steps)} = 2^\text{number of steps} + 1 $$
61+
62+
- When `number of steps` are above the limit, the pattern is
63+
the sum of latest `number of steps` previous calls of
64+
`stepPerms(x)` results as follows:
65+
66+
$$ \displaystyle\sum_{
67+
i=\text{number of steps} - \text{limit}}
68+
^\text{number of steps}
69+
stepPerms(\text{number of steps} - i)
70+
$$
Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,39 @@
11
# pylint: disable=line-too-long
22
# @link Problem definition [[docs/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci-recursive-staircase.md]] # noqa
3+
# @see Solution Notes: [[docs/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci-recursive-staircase-solution-notes.md]] # noqa
34
# pylint: enable=line-too-long
45

56
from typing import Dict
67

8+
TOP_LIMIT = 10 ** 10 + 7
9+
STEPS_LIMIT = 3
710

8-
def step_perms_comput_with_cache(n_steps: int, cache: Dict[int, int]) -> int:
11+
12+
def step_perms_comput_with_cache(
13+
n_steps: int,
14+
cache: Dict[int, int],
15+
steps_limit: int) -> int:
16+
17+
# Base cases
918
if 0 <= n_steps <= 2:
1019
return n_steps
11-
if n_steps == 3:
12-
return 4
1320

1421
result = 0
15-
for i in range(1, 4):
16-
if n_steps - i not in cache:
17-
cache[n_steps - i] = step_perms_comput_with_cache(n_steps - i, cache)
22+
for i in range(1, min(steps_limit, n_steps) + 1):
23+
search_key: int = n_steps - i
24+
25+
if search_key not in cache:
26+
cache[search_key] = step_perms_comput_with_cache(
27+
search_key,
28+
cache,
29+
steps_limit
30+
)
1831

19-
result += cache[n_steps - i]
32+
result += cache[search_key]
2033

21-
return result
34+
return (result + 1) if n_steps <= steps_limit else result
2235

2336

2437
def step_perms(n_steps: int) -> int:
2538
initial_cache: Dict[int, int] = {}
26-
return step_perms_comput_with_cache(n_steps, initial_cache) % (10 ** 10 + 7)
39+
return step_perms_comput_with_cache(n_steps, initial_cache, STEPS_LIMIT) % TOP_LIMIT
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
[
2+
{
3+
"title": "Sample Test case 0",
4+
"tests": [
5+
{
6+
"input": 1,
7+
"expected": 1
8+
},
9+
{
10+
"input": 3,
11+
"expected": 4
12+
},
13+
{
14+
"input": 7,
15+
"expected": 44
16+
}
17+
]
18+
},
19+
{
20+
"title": "Sample Test case 9",
21+
"tests": [
22+
{
23+
"input": 5,
24+
"expected": 13
25+
},
26+
{
27+
"input": 8,
28+
"expected": 81
29+
}
30+
]
31+
},
32+
{
33+
"title": "Sample Test case 10",
34+
"tests": [
35+
{
36+
"input": 15,
37+
"expected": 5768
38+
},
39+
{
40+
"input": 20,
41+
"expected": 121415
42+
},
43+
{
44+
"input": 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+
"input": 4,
7+
"limit": 3,
8+
"expected": 7
9+
}
10+
]
11+
},
12+
{
13+
"title": "Own sample 2",
14+
"tests": [
15+
{
16+
"input": 5,
17+
"limit": 4,
18+
"expected": 15
19+
}
20+
]
21+
},
22+
{
23+
"title": "Own sample 3",
24+
"tests": [
25+
{
26+
"input": 6,
27+
"limit": 2,
28+
"expected": 13
29+
}
30+
]
31+
}
32+
]
Lines changed: 41 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,26 @@
11
import unittest
2-
from .ctci_recursive_staircase import step_perms
2+
import json
3+
from pathlib import Path
4+
from typing import Dict
5+
6+
from .ctci_recursive_staircase import step_perms, step_perms_comput_with_cache
37
from .ctci_recursive_staircase_alternative import step_perms as step_perms_alt
48

5-
TEST_CASES = [
6-
{
7-
'title': 'Sample Test case 0',
8-
'tests': [
9-
{
10-
'input': 1,
11-
'answer': 1
12-
},
13-
{
14-
'input': 3,
15-
'answer': 4
16-
},
17-
{
18-
'input': 7,
19-
'answer': 44
20-
}
21-
]
22-
},
23-
{
24-
'title': 'Sample Test case 9',
25-
'tests': [
26-
{
27-
'input': 5,
28-
'answer': 13
29-
},
30-
{
31-
'input': 8,
32-
'answer': 81
33-
}
34-
]
35-
},
36-
{
37-
'title': 'Sample Test case 10',
38-
'tests': [
39-
{
40-
'input': 15,
41-
'answer': 5768
42-
},
43-
{
44-
'input': 20,
45-
'answer': 121415
46-
},
47-
{
48-
'input': 27,
49-
'answer': 8646064
50-
}
51-
]
52-
}
53-
]
9+
FILE_PATH = str(Path(__file__).resolve().parent)
10+
11+
with open(
12+
FILE_PATH +
13+
'/ctci_recursive_staircase.testcases.json',
14+
encoding="utf-8"
15+
) as file1:
16+
TEST_CASES = json.load(file1)
17+
18+
with open(
19+
FILE_PATH +
20+
'/ctci_recursive_staircase_generalized.testcases.json',
21+
encoding="utf-8"
22+
) as file2:
23+
TEST_CASES_GENERALIZED = json.load(file2)
5424

5525

5626
class TestRecursionFibonacciNumbers(unittest.TestCase):
@@ -73,9 +43,26 @@ def test_step_perms(self):
7343
for _, _tt in enumerate(testset['tests']):
7444

7545
self.assertEqual(
76-
step_perms(_tt['input']), _tt['answer'],
46+
step_perms(_tt['input']), _tt['expected'],
7747
f"{_} | step_perms({_tt['input']}) must be "
78-
f"=> {_tt['answer']} in {testset['title']}")
48+
f"=> {_tt['expected']} in {testset['title']}")
49+
50+
def test_step_perms_comput_with_cache(self):
51+
52+
for _, testset in enumerate(TEST_CASES_GENERALIZED):
53+
54+
for _, _tt in enumerate(testset['tests']):
55+
56+
initial_cache: Dict[int, int] = {}
57+
58+
self.assertEqual(
59+
step_perms_comput_with_cache(
60+
_tt['input'], initial_cache,
61+
_tt['limit']),
62+
_tt['expected'],
63+
f"{_} | step_perms_comput_with_cache("
64+
f"{_tt['input']}, {initial_cache}, {_tt['limit']}) must be "
65+
f"=> {_tt['expected']} in {testset['title']}")
7966

8067
def test_step_perms_alt(self):
8168

@@ -84,6 +71,6 @@ def test_step_perms_alt(self):
8471
for _, _tt in enumerate(testset['tests']):
8572

8673
self.assertEqual(
87-
step_perms_alt(_tt['input']), _tt['answer'],
74+
step_perms_alt(_tt['input']), _tt['expected'],
8875
f"{_} | step_perms_alt({_tt['input']}) must be "
89-
f"=> {_tt['answer']} in {testset['title']}")
76+
f"=> {_tt['expected']} in {testset['title']}")

0 commit comments

Comments
 (0)