{x}
blog image

Knight Dialer

The chess knight 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). The possible movements of chess knight are shown in this diagram:

A chess knight can move as indicated in the chess diagram below:

We have a chess knight and a phone pad as shown below, the knight can only stand on a numeric cell (i.e. blue cell).

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.

 

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.

 

Constraints:

  • 1 <= n <= 5000

Solution Explanation for Knight Dialer

This problem asks to find the number of distinct phone numbers of length n that can be dialed using a knight on a phone keypad. The knight's movement is restricted to the valid "L" shaped moves on the keypad.

Approach 1: Dynamic Programming (Recursive)

This approach uses dynamic programming to iteratively build up the count of valid phone numbers.

Intuition:

The number of ways to dial a number of length n depends on the number of ways to dial a number of length n-1. We can build a recursive relationship.

Algorithm:

  1. Initialization: Create an array f of size 10 (representing the digits 0-9) initialized to 1. This represents that there's 1 way to dial a number of length 1 for each digit.
  2. Iteration: Iterate n-1 times. In each iteration:
    • Create a new array g of size 10, initialized to 0.
    • For each digit i (0-9), calculate the number of ways to reach it from the previous digits using the knight's movement rules, summing up the values from f corresponding to the valid previous digits. This is the core recursive step. For example, to reach 0, you can only come from 4 and 6. Therefore, g[0] = f[4] + f[6].
    • Update f to g for the next iteration.
  3. Result: Finally, sum up all the elements in f and take the modulo with 10^9 + 7.

Time Complexity: O(n) - The outer loop runs n-1 times, and the inner loop iterates through 10 digits in each iteration.

Space Complexity: O(1) - We use constant extra space for arrays f and g.

Approach 2: Matrix Exponentiation

This approach leverages matrix exponentiation to significantly optimize the dynamic programming solution. It exploits the fact that the transitions between digits can be represented as a matrix.

Intuition:

The transitions between digits form a directed graph where each digit is a node and the edges represent valid knight moves. We can represent this graph as an adjacency matrix. The number of paths of length n can be found by raising the adjacency matrix to the power n.

Algorithm:

  1. Adjacency Matrix: Create a 10x10 adjacency matrix (base) where base[i][j] = 1 if a knight can move from digit i to digit j, otherwise 0.
  2. Matrix Exponentiation: Raise the adjacency matrix to the power n-1 efficiently using matrix exponentiation (similar to binary exponentiation for numbers). This is done by repeatedly squaring the matrix and multiplying when needed, reducing the number of multiplications from O(n) to O(log n).
  3. Result: Sum the elements of the resulting matrix's first row (or any row since it's symmetric in this case), and take the modulo with 10^9 + 7.

Time Complexity: O(log n) - Matrix exponentiation takes logarithmic time. Matrix multiplication is O(10^3), which is constant.

Space Complexity: O(10^2) - We use constant extra space for the matrix.

Code Examples (Python)

Approach 1 (Dynamic Programming):

class Solution:
    def knightDialer(self, n: int) -> int:
        f = [1] * 10
        for _ in range(n - 1):
            g = [0] * 10
            g[0] = f[4] + f[6]
            g[1] = f[6] + f[8]
            g[2] = f[7] + f[9]
            g[3] = f[4] + f[8]
            g[4] = f[0] + f[3] + f[9]
            g[6] = f[0] + f[1] + f[7]
            g[7] = f[2] + f[6]
            g[8] = f[1] + f[3]
            g[9] = f[2] + f[4]
            f = g
        return sum(f) % (10**9 + 7)

Approach 2 (Matrix Exponentiation): (requires NumPy)

import numpy as np
 
class Solution:
    def knightDialer(self, n: int) -> int:
        base = np.array([
            [0, 0, 0, 0, 1, 0, 1, 0, 0, 0],
            [0, 0, 0, 0, 0, 0, 1, 0, 1, 0],
            [0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
            [0, 0, 0, 0, 1, 0, 0, 0, 1, 0],
            [1, 0, 0, 1, 0, 0, 0, 0, 0, 1],
            [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            [1, 1, 0, 0, 0, 0, 0, 1, 0, 0],
            [0, 0, 1, 0, 0, 0, 1, 0, 0, 0],
            [0, 1, 0, 1, 0, 0, 0, 0, 0, 0],
            [0, 0, 1, 0, 1, 0, 0, 0, 0, 0],
        ])
 
        res = np.linalg.matrix_power(base, n - 1) % (10**9 + 7)
        return np.sum(res[0]) % (10**9 + 7)
 

The code in other languages (Java, C++, Go, TypeScript, C#) will follow similar structures for both approaches, with the appropriate data structure and syntax changes. The matrix exponentiation approach is considerably faster for larger values of n.