You are given three integers: N
(the size of an N x N
chessboard), K
(the number of moves), and the starting position (row, column)
of a knight on the board.
A knight can move in 8 possible directions, as in chess. At each move, the knight chooses one of the 8 directions uniformly at random (if it stays on the board). If the knight moves off the board, it is immediately removed and cannot move again.
The task is to determine the probability that the knight remains on the board after exactly K
moves, starting from (row, column)
.
1 <= N <= 25
0 <= K <= 100
0 <= row, column < N
The naive way to solve this problem is to simulate all possible paths the knight could take, counting how many end up on the board after K
moves, and then dividing by the total number of possible paths. However, the number of possible paths grows exponentially with K
(specifically, 8K), making brute-force infeasible for larger values of K
.
Instead, we look for overlapping subproblems and optimal substructure, which suggests using dynamic programming (DP). At each step, the probability of being on the board at a given position depends only on the probabilities from the previous step. This makes it possible to build up the solution step by step, avoiding redundant calculations.
By storing the probability of being at each position after each move, we can efficiently compute the answer without simulating every possible path.
We solve this problem using a bottom-up dynamic programming approach. Here’s how:
dp[k][r][c]
be the probability that the knight is on the board at position (r, c)
after k
moves.dp[0][row][column] = 1
.k
from 1 to K
, for every cell (r, c)
:
(nr, nc)
is on the board, add dp[k-1][nr][nc] / 8
to dp[k][r][c]
.dp[K][r][c]
over all valid board positions (r, c)
.This approach ensures we efficiently compute the probability without redundant calculations.
Example Input:
N = 3, K = 2, row = 0, column = 0
0.0625
(or 6.25%).
This process shows how probabilities are distributed and accumulated at each step.
K
, since each move branches into 8 possibilities.The DP approach is efficient and works well within the given constraints.
This problem is a classic example where dynamic programming transforms an exponential brute-force problem into a feasible polynomial-time solution. By modeling the knight's probabilities as states and transitions, we efficiently accumulate the total probability of staying on the board. The key insight is to use previous probabilities to build up the answer step by step, leveraging overlapping subproblems and optimal substructure.
class Solution:
def knightProbability(self, N: int, K: int, row: int, column: int) -> float:
directions = [
(2, 1), (1, 2), (-1, 2), (-2, 1),
(-2, -1), (-1, -2), (1, -2), (2, -1)
]
dp_prev = [[0.0 for _ in range(N)] for _ in range(N)]
dp_prev[row][column] = 1.0
for step in range(1, K+1):
dp_curr = [[0.0 for _ in range(N)] for _ in range(N)]
for r in range(N):
for c in range(N):
if dp_prev[r][c] > 0:
for dr, dc in directions:
nr, nc = r + dr, c + dc
if 0 <= nr < N and 0 <= nc < N:
dp_curr[nr][nc] += dp_prev[r][c] / 8.0
dp_prev = dp_curr
return sum(map(sum, dp_prev))
class Solution {
public:
double knightProbability(int N, int K, int row, int column) {
vector<vector<double>> dp_prev(N, vector<double>(N, 0.0));
dp_prev[row][column] = 1.0;
vector<pair<int,int>> dirs = {{2,1},{1,2},{-1,2},{-2,1},{-2,-1},{-1,-2},{1,-2},{2,-1}};
for (int step = 1; step <= K; ++step) {
vector<vector<double>> dp_curr(N, vector<double>(N, 0.0));
for (int r = 0; r < N; ++r) {
for (int c = 0; c < N; ++c) {
if (dp_prev[r][c] > 0) {
for (auto d : dirs) {
int nr = r + d.first, nc = c + d.second;
if (nr >= 0 && nr < N && nc >= 0 && nc < N) {
dp_curr[nr][nc] += dp_prev[r][c] / 8.0;
}
}
}
}
}
dp_prev = dp_curr;
}
double ans = 0.0;
for (int r = 0; r < N; ++r)
for (int c = 0; c < N; ++c)
ans += dp_prev[r][c];
return ans;
}
};
class Solution {
public double knightProbability(int N, int K, int row, int column) {
double[][] dpPrev = new double[N][N];
dpPrev[row][column] = 1.0;
int[][] dirs = {{2,1},{1,2},{-1,2},{-2,1},{-2,-1},{-1,-2},{1,-2},{2,-1}};
for (int step = 1; step <= K; ++step) {
double[][] dpCurr = new double[N][N];
for (int r = 0; r < N; ++r) {
for (int c = 0; c < N; ++c) {
if (dpPrev[r][c] > 0) {
for (int[] d : dirs) {
int nr = r + d[0], nc = c + d[1];
if (nr >= 0 && nr < N && nc >= 0 && nc < N) {
dpCurr[nr][nc] += dpPrev[r][c] / 8.0;
}
}
}
}
}
dpPrev = dpCurr;
}
double ans = 0.0;
for (int r = 0; r < N; ++r)
for (int c = 0; c < N; ++c)
ans += dpPrev[r][c];
return ans;
}
}
var knightProbability = function(N, K, row, column) {
const dirs = [[2,1],[1,2],[-1,2],[-2,1],[-2,-1],[-1,-2],[1,-2],[2,-1]];
let dpPrev = Array.from({length: N}, () => Array(N).fill(0));
dpPrev[row][column] = 1.0;
for (let step = 1; step <= K; ++step) {
let dpCurr = Array.from({length: N}, () => Array(N).fill(0));
for (let r = 0; r < N; ++r) {
for (let c = 0; c < N; ++c) {
if (dpPrev[r][c] > 0) {
for (let d of dirs) {
let nr = r + d[0], nc = c + d[1];
if (nr >= 0 && nr < N && nc >= 0 && nc < N) {
dpCurr[nr][nc] += dpPrev[r][c] / 8.0;
}
}
}
}
}
dpPrev = dpCurr;
}
let ans = 0.0;
for (let r = 0; r < N; ++r)
for (let c = 0; c < N; ++c)
ans += dpPrev[r][c];
return ans;
};