The knight in chess has a unique movement, it may move two squares vertically and one square horizontally, or two squares horizontally and one square vertically (with both forming the shape of an L). Given a chess knight and a phone pad as shown below, the knight can only stand on a numeric cell

1 2 3
4 5 6
7 9 0
* 0 #

Given an integer n, return how many distinct phone numbers of length n we can dial. You are allowed to place the knight on any numeric cell initially and then you should perform n - 1 jumps to dial a number of length n. All jumps should be valid knight jumps. As the answer may be very large, return the answer modulo 109 + 7.

Examples

Example 1:

Input: n = 1
Output: 10
Explanation: We need to dial a number of length 1, so placing the knight over any numeric cell of the 10 cells is sufficient.
Example 2:

Input: n = 2
Output: 20
Explanation: All the valid number we can dial are [04, 06, 16, 18, 27, 29, 34, 38, 40, 43, 49, 60, 61, 67, 72, 76, 81, 83, 92, 94]

Example 3:

Input: n = 3131
Output: 136006598
Explanation: Please take care of the mod.

Intuition

There is an excellent blog post written by a former Google Interview Alex Golec. His experience with this problem is invaluable and a must read to best understand this question.

We need to be able to derive our legal moves from any given location on the key pad, a map would be appropriate

paths = {
    -1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
    0: [4, 6],
    1: [6, 8],
    2: [7, 9],
    3: [4, 8],
    4: [0, 3, 9],
    5: [],
    6: [0, 1, 7],
    7: [2, 6],
    8: [1, 3],
    9: [2, 4]
}

A brute force recursive solution

import pytest

from paths import paths


class Solution:

    def knightDialer(self, n: int, position=-1) -> int:
        if n == 0:
            return 1

        count = 0
        for neighbor in paths[position]:
            count += self.knightDialer(n - 1, neighbor)

        return count % (10 ** 9 + 7)


@pytest.mark.parametrize("jumps,expected_result", [
    (1, 10),
    (2, 20),
])
class TestClass:
    def test_simple_case(self, jumps, expected_result):
        s = Solution()
        result = s.knightDialer(jumps)
        assert result == expected_result

The previous solution with memoization

import pytest

from paths import paths

class Solution:

    cache = {}

    def knightDialer(self, n: int) -> int:
        return self.helper(n, -1) % (10 ** 9 + 7)

    def helper(self, idx, curr):

        if (idx, curr) in self.cache:
            return self.cache[(idx, curr)]

        if idx == 0:
            return 1

        count = 0
        for num in paths[curr]:
            count += self.helper(idx - 1, num)

        self.cache[(idx, curr)] = count
        return count


@pytest.mark.parametrize("jumps,expected_result", [
    (1, 10),
    (2, 20),
])
class TestClass:
    def test_simple_case(self, jumps, expected_result):
        s = Solution()
        result = s.knightDialer(jumps)
        assert result == expected_result

A solution with Dynamic Programming

import pytest
from paths import paths


class Solution:

    def count_hops(self, n: int) -> int:
        num_keys = 10
        all_jumps = [1] * num_keys

        for _ in range(n - 1):
            local_jumps = [0] * num_keys
            for i in paths:
                if i == -1: continue
                for j in paths[i]:
                    local_jumps[i] += all_jumps[j]
            all_jumps = local_jumps

        return sum(all_jumps) % (10 ** 9 + 7)


@pytest.mark.parametrize("test_args,expected", [
    (2, 20),
    (1, 10),
])
class TestClass:
    def test_simple_case(self, test_args, expected):
        s = Solution()
        assert s.count_hops(test_args) == expected