Problem
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 diagaram:
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).
$$ \begin{bmatrix} \colorbox{blue} 1 & \colorbox{blue} 2 & \colorbox{blue}3 \\ \colorbox{blue} 4 & \colorbox{blue} 5 & \colorbox{blue}6 \\ \colorbox{blue} 7 & \colorbox{blue} 8 & \colorbox{blue}9 \\ \colorbox{red} * & \colorbox{blue} 0 & \colorbox{red} # \end{bmatrix} $$
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 10^9 + 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.
Solution
Method 1 - Recursion
The knight can only stand on numeric keys (blue cells), and it moves in an “L” shape, which means:
- Two squares in one direction and one square perpendicular, or
- One square in one direction and two squares perpendicular.
Based on that we can we define moves[]
matrix. Then for each element moves[i]
we get following knight moves:
- from
0
we can either go to cells4
or6
. Hencemoves[0] = [4, 6]
- from
1
we can either go to cells6
or8
. Hencemoves[1] = [6, 8]
So, this is how we can define our moves:
moves = [
[4, 6], # 0
[6, 8], # 1
[7, 9], # 2
[4, 8], # 3
[3, 9, 0], # 4
[], # 5 (not used)
[1, 7, 0], # 6
[2, 6], # 7
[1, 3], # 8
[2, 4] # 9
]
Code
Java
class Solution {
private static final int[][] MOVES = {
{4, 6}, // 0
{6, 8}, // 1
{7, 9}, // 2
{4, 8}, // 3
{3, 9, 0}, // 4
{}, // 5 (not used)
{1, 7, 0}, // 6
{2, 6}, // 7
{1, 3}, // 8
{2, 4} // 9
};
private static final int MOD = 1000000007;
public int knightDialer(int n) {
int result = 0;
for (int i = 0; i <= 9; i++) {
result = (result + knightDialerFrom(i, n)) % MOD;
}
return result;
}
private int knightDialerFrom(int current, int n) {
if (n == 1) return 1;
int total = 0;
for (int next: MOVES[current]) {
total = (total + knightDialerFrom(next, n - 1)) % MOD;
}
return total;
}
}
Complexity
- ⏰ Time complexity:
O(9^n)
- For each digit, the knight can make up to 2 or 3 jumps based on the
moves
array. - Let
f(n, d)
be the number of ways to reach a number of lengthn
starting at digitd
. - Without memoization, the total number of calls can be very large as it forms a tree-like structure.
- In general, for
n
moves, the number of recursive calls can be approximated byO(9^n)
in the worst case, because of the overlapping subproblems and exponential growth in recursive calls.
- For each digit, the knight can make up to 2 or 3 jumps based on the
- 🧺 Space complexity:
O(n)
, assuming recursion stack
Method 2 - Top Down DP with memo
Code
Java
class Solution {
private static final int[][] MOVES = {
{4, 6}, // 0
{6, 8}, // 1
{7, 9}, // 2
{4, 8}, // 3
{3, 9, 0}, // 4
{}, // 5 (not used)
{1, 7, 0}, // 6
{2, 6}, // 7
{1, 3}, // 8
{2, 4} // 9
};
private static final int MOD = 1000000007;
private int[][] memo;
public int knightDialer(int n) {
memo = new int[n + 1][10];
for (int[] row: memo) {
Arrays.fill(row, -1);
}
int result = 0;
for (int i = 0; i <= 9; i++) {
result = (result + knightDialerFrom(i, n)) % MOD;
}
return result;
}
private int knightDialerFrom(int current, int n) {
if (n == 1) return 1;
if (memo[n][current] != -1) return memo[n][current];
int total = 0;
for (int next: MOVES[current]) {
total = (total + knightDialerFrom(next, n - 1)) % MOD;
}
memo[n][current] = total;
return total;
}
}
Complexity
- ⏰ Time complexity:
O(n)
- Each state
(n, d)
is computed at most once and stored inmemo
. - There are
10*n
possible states. - For each state
(n, d)
, we iterate over a constant list of knight moves (at most 3 moves). So, we getO(10*n)
=O(n)
- Each state
- 🧺 Space complexity:
O(n)
- The memoization table, which stores results for all states
(n, d)
, which takesO(10n)
- The recursion stack depth, which is
n
.
- The memoization table, which stores results for all states
Method 3 - Bottom up DP
The idea is to track the number of ways to reach each numeric cell on the phone pad with a certain number of moves, leveraging the unique movement capabilities of a chess knight.
Here are the steps we can take:
Initialization:
- Create a DP table where
dp[i][j]
represents the number of ways to reach cellj
withi
moves. - Initialize the base cases where
dp[0][j] = 1
for all numeric keysj
(since there is 1 way to be on a key initially without any moves).
- Create a DP table where
Valid Moves:
- Define the possible knight moves from each numeric cell. These are the valid transitions based on knight’s movement rules.
DP Transition:
- Update the DP table based on possible moves. For each move count
i
, updatedp[i+1]
(next move count) based on current move countdp[i]
using the valid knight moves.
- Update the DP table based on possible moves. For each move count
Result Calculation:
- Sum the number of ways to end up on any numeric key after
n-1
moves (since another move is required to reach a phone number of lengthn
).
- Sum the number of ways to end up on any numeric key after
Modulo Operation:
- Since the result can be large, take the result modulo (10^9 + 7).
Code
Java
class Solution {
private static final int[][] MOVES = {
{4, 6}, // 0
{6, 8}, // 1
{7, 9}, // 2
{4, 8}, // 3
{3, 9, 0}, // 4
{}, // 5 (not used)
{1, 7, 0}, // 6
{2, 6}, // 7
{1, 3}, // 8
{2, 4} // 9
};
private static final int MOD = 1000000007;
public int knightDialer(int n) {
// Initialize DP table
int[][] dp = new int[n + 1][10];
// Base case: 1 way to dial a number with length 1 (initial position)
for (int i = 0; i <= 9; i++) {
dp[1][i] = 1;
}
// Fill DP table
for (int i = 1; i < n; i++) {
for (int j = 0; j <= 9; j++) {
if (dp[i][j] > 0) {
for (int next: moves[j]) {
dp[i + 1][next] = (dp[i + 1][next] + dp[i][j]) % MOD;
}
}
}
}
// Sum all ways to dial a number of length n
long result = 0;
for (int i = 0; i <= 9; i++) {
result = (result + dp[n][i]) % MOD;
}
return (int) result;
}
}
Complexity
- ⏰ Time complexity:
O(n)
- 🧺 Space complexity:
O(n)