Skip to content
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
Expand Up @@ -36,3 +36,35 @@ 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
@@ -1,26 +1,39 @@
# pylint: disable=line-too-long
# @link Problem definition [[docs/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci-recursive-staircase.md]] # noqa
# @see Solution Notes: [[docs/hackerrank/interview_preparation_kit/recursion_and_backtracking/ctci-recursive-staircase-solution-notes.md]] # noqa
# pylint: enable=line-too-long

from typing import Dict

TOP_LIMIT = 10 ** 10 + 7
STEPS_LIMIT = 3

def step_perms_comput_with_cache(n_steps: int, cache: Dict[int, int]) -> int:

def step_perms_comput_with_cache(
n_steps: int,
cache: Dict[int, int],
steps_limit: int) -> int:

# Base cases
if 0 <= n_steps <= 2:
return n_steps
if n_steps == 3:
return 4

result = 0
for i in range(1, 4):
if n_steps - i not in cache:
cache[n_steps - i] = step_perms_comput_with_cache(n_steps - i, cache)
for i in range(1, min(steps_limit, n_steps) + 1):
search_key: int = n_steps - i

if search_key not in cache:
cache[search_key] = step_perms_comput_with_cache(
search_key,
cache,
steps_limit
)

result += cache[n_steps - i]
result += cache[search_key]

return result
return (result + 1) if n_steps <= steps_limit else result


def step_perms(n_steps: int) -> int:
initial_cache: Dict[int, int] = {}
return step_perms_comput_with_cache(n_steps, initial_cache) % (10 ** 10 + 7)
return step_perms_comput_with_cache(n_steps, initial_cache, STEPS_LIMIT) % TOP_LIMIT
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,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
}
]
}
]
Original file line number Diff line number Diff line change
@@ -1,56 +1,26 @@
import unittest
from .ctci_recursive_staircase import step_perms
import json
from pathlib import Path
from typing import Dict

from .ctci_recursive_staircase import step_perms, step_perms_comput_with_cache
from .ctci_recursive_staircase_alternative import step_perms as step_perms_alt

TEST_CASES = [
{
'title': 'Sample Test case 0',
'tests': [
{
'input': 1,
'answer': 1
},
{
'input': 3,
'answer': 4
},
{
'input': 7,
'answer': 44
}
]
},
{
'title': 'Sample Test case 9',
'tests': [
{
'input': 5,
'answer': 13
},
{
'input': 8,
'answer': 81
}
]
},
{
'title': 'Sample Test case 10',
'tests': [
{
'input': 15,
'answer': 5768
},
{
'input': 20,
'answer': 121415
},
{
'input': 27,
'answer': 8646064
}
]
}
]
FILE_PATH = str(Path(__file__).resolve().parent)

with open(
FILE_PATH +
'/ctci_recursive_staircase.testcases.json',
encoding="utf-8"
) as file1:
TEST_CASES = json.load(file1)

with open(
FILE_PATH +
'/ctci_recursive_staircase_generalized.testcases.json',
encoding="utf-8"
) as file2:
TEST_CASES_GENERALIZED = json.load(file2)


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

self.assertEqual(
step_perms(_tt['input']), _tt['answer'],
step_perms(_tt['input']), _tt['expected'],
f"{_} | step_perms({_tt['input']}) must be "
f"=> {_tt['answer']} in {testset['title']}")
f"=> {_tt['expected']} in {testset['title']}")

def test_step_perms_comput_with_cache(self):

for _, testset in enumerate(TEST_CASES_GENERALIZED):

for _, _tt in enumerate(testset['tests']):

initial_cache: Dict[int, int] = {}

self.assertEqual(
step_perms_comput_with_cache(
_tt['input'], initial_cache,
_tt['limit']),
_tt['expected'],
f"{_} | step_perms_comput_with_cache("
f"{_tt['input']}, {initial_cache}, {_tt['limit']}) must be "
f"=> {_tt['expected']} in {testset['title']}")

def test_step_perms_alt(self):

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

self.assertEqual(
step_perms_alt(_tt['input']), _tt['answer'],
step_perms_alt(_tt['input']), _tt['expected'],
f"{_} | step_perms_alt({_tt['input']}) must be "
f"=> {_tt['answer']} in {testset['title']}")
f"=> {_tt['expected']} in {testset['title']}")