题目地址:
https://leetcode.com/problems/knight-probability-in-chessboard/
给定一个 N × N N\times N N×N的棋盘,有一个马在坐标 ( r , c ) (r,c) (r,c)上,它可以走 k k k步,问走过 k k k步仍然在棋盘上的概率(中途不能出界)。
法1:动态规划。设 f [ s ] [ i ] [ j ] f[s][i][j] f[s][i][j]是该马从 ( r , c ) (r,c) (r,c)出发,走 s s s步,走到 ( i , j ) (i,j) (i,j)这个位置的概率。那么该马可以从八个方向过来(在不出界的情况下,出界的情况判断一下即可),则有: f [ s ] [ i ] [ j ] = 1 8 ∑ ( x , y ) → ( i , j ) f [ s − 1 ] [ x ] [ y ] f[s][i][j]=\frac{1}{8}\sum_{(x,y)\to (i,j)} f[s-1][x][y] f[s][i][j]=81(x,y)→(i,j)∑f[s−1][x][y]边界条件 f [ 0 ] [ r ] [ c ] = 1 f[0][r][c]=1 f[0][r][c]=1, f [ 0 ] f[0] f[0]其余位置都是 0 0 0。最后的答案就是 f [ k ] f[k] f[k]的所有位置数字和。此外,还可以用滚动数组优化空间。代码如下:
public class Solution {
public double knightProbability(int N, int K, int r, int c) {
double[][][] dp = new double[2][N][N];
dp[0][r][c] = 1.0;
// 存八个方向
int[][] dir = {{1, 2}, {2, 1}, {1, -2}, {2, -1}, {-1, 2}, {-2, 1}, {-1, -2}, {-2, -1}};
for (int step = 1; step <= K; step++) {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
dp[step & 1][i][j] = 0;
for (int k = 0; k < dir.length; k++) {
int prevX = i + dir[k][0], prevY = j + dir[k][1];
if (0 <= prevX && prevX < N && 0 <= prevY && prevY < N) {
dp[step & 1][i][j] += dp[step - 1 & 1][prevX][prevY];
}
}
dp[step & 1][i][j] *= 0.125;
}
}
}
double res = 0.0;
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
res += dp[K & 1][i][j];
}
}
return res;
}
}
时间复杂度 O ( k N 2 ) O(kN^2) O(kN2),空间 O ( N 2 ) O(N^2) O(N2)。
法2:记忆化搜索。设 f [ s ] [ i ] [ j ] f[s][i][j] f[s][i][j]是从 ( i , j ) (i,j) (i,j)出发,走 s s s步仍然在界内的概率。那么可以按照下一步走到哪儿做分类,每个方向的概率是 1 8 \frac{1}{8} 81,所以按照全概率公式: f [ s ] [ i ] [ j ] = 1 8 ∑ ( i , j ) → ( x , y ) f [ s − 1 ] [ x ] [ y ] f[s][i][j]=\frac{1}{8}\sum_{(i,j)\to (x,y)}f[s-1][x][y] f[s][i][j]=81(i,j)→(x,y)∑f[s−1][x][y]边界条件是 f [ 0 ] [ i ] [ j ] = 1 f[0][i][j]=1 f[0][i][j]=1,其中 0 ≤ i < N , 0 ≤ j < N 0\le i< N, 0\le j< N 0≤i<N,0≤j<N。代码如下:
public class Solution {
public double knightProbability(int N, int K, int r, int c) {
// dp[s][i][j]走s步,从(i, j)出发的情况下,走到界内的概率。这个数组作记忆化之用
double[][][] dp = new double[K + 1][N][N];
int[][] dir = {{1, 2}, {2, 1}, {1, -2}, {2, -1}, {-1, 2}, {-2, 1}, {-1, -2}, {-2, -1}};
return dfs(r, c, N, K, dp, dir);
}
// 返回走step步,从(x, y)出发的情况下,走到界内的概率
private double dfs(int x, int y, int N, int step, double[][][] dp, int[][] dir) {
if (step == 0) {
// 做记忆
dp[0][x][y] = 1.0;
return 1.0;
}
// 有记忆,则返回记忆
if (dp[step][x][y] > 0) {
return dp[step][x][y];
}
double p = 0.0;
for (int i = 0; i < dir.length; i++) {
int nextX = x + dir[i][0], nextY = y + dir[i][1];
if (inBound(nextX, nextY, N)) {
p += dfs(nextX, nextY, N, step - 1, dp, dir);
}
}
p *= 0.125;
// 返回之前做记忆
dp[step][x][y] = p;
return p;
}
private boolean inBound(int x, int y, int N) {
return 0 <= x && x < N && 0 <= y && y < N;
}
}
时空复杂度 O ( k N 2 ) O(kN^2) O(kN2)。