在一个 n x n 的国际象棋棋盘上,一个骑士从单元格 (row, column) 开始,并尝试进行 k 次移动。行和列是 从 0 开始 的,所以左上单元格是 (0,0) ,右下单元格是 (n - 1, n - 1) 。
象棋骑士有8种可能的走法,如下图所示。每次移动在基本方向上是两个单元格,然后在正交方向上是一个单元格。
每次骑士要移动时,它都会随机从8种可能的移动中选择一种(即使棋子会离开棋盘),然后移动到那里。
骑士继续移动,直到它走了 k 步或离开了棋盘。
返回 骑士在棋盘停止移动后仍留在棋盘上的概率 。
示例 1:
输入: n = 3, k = 2, row = 0, column = 0
输出: 0.0625
解释: 有两步(到(1,2),(2,1))可以让骑士留在棋盘上。
在每一个位置上,也有两种移动可以让骑士留在棋盘上。
骑士留在棋盘上的总概率是0.0625。
示例 2:
输入: n = 1, k = 0, row = 0, column = 0
输出: 1.00000
提示:
- 1 <= n <= 25
- 0 <= k <= 100
- 0 <= row, column <= n
分析:
方法:动态规划+迭代
令 dp[ i ][ j ][ k ] 表示从位置 ( i, j ) 走 k 步仍在棋盘上的概率,因为棋子走 8 步,所以走下一步时每一步占的比重就为 1 / 8,因此 dp[ i ][ j ][ k ] 的值就是 dp[ i - 2 ][ j - 1][ k - 1] + dp[ i - 1][ j - 2 ][ k - 1] + ... 每一步的概率之和除以 8,令下一步的位置为 ( x, y ) ,可以写出转移方程:
dp[ i ][ j ][ k ] = ∑ dp[ x ][ y ][ k - 1 ] / 8
时间复杂度:O(n^2 * k * C) C 为 8
空间复杂度:O(n^2 * k)
class Solution {
//可以走的位置
int[][] dirs = new int[][]{{-2, -1}, {-1, -2}, {1, -2}, {2, -1}, {-2, 1}, {-1, 2}, {2, 1}, {1, 2}};
public double knightProbability(int n, int k, int row, int column) {
//创建dp数组
double[][][] dp = new double[n][n][k+1];
//最后一步的概率为1
for(int i = 0; i < n; ++i){
for(int j = 0; j < n; ++j){
dp[i][j][0] = 1.0;
}
}
//遍历
for(int l = 1; l <= k; ++l){
for(int i = 0; i < n; ++i){
for(int j = 0; j < n; ++j){
for(int[] dir: dirs){
//记录新的位置
int x = i + dir[0], y = j + dir[1];
//未越界
if(x >= 0 && y >= 0 && x < n && y < n){
dp[i][j][l] += dp[x][y][l-1] / 8.0;
}
}
}
}
}
//返回结果
return dp[row][column][k];
}
}
方法2:动态规划+记忆化搜索+DFS
方法1 将每个点出发的概率都计算了出来,实际上我们只需要我们给定点出发的概率,因此我们可以采用深度遍历(DFS)的方式,从给定点出发,深度遍历会优先遍历到最底层,即 k == 0 时,此时的概率为 1,然后按 k 递增的顺序回溯,每层遍历可以用 dp 方程来计算当层的概率。越界时直接返回 0,重复遍历时返回 dp 数组的值即可。
时间复杂度:O(n^2 * k)
空间复杂度:O(n^2 * k)
class Solution {
//可以走的位置
int[][] dirs = new int[][]{{-2, -1}, {-1, -2}, {1, -2}, {2, -1}, {-2, 1}, {-1, 2}, {2, 1}, {1, 2}};
//dp数组
double[][][] dp;
//记录棋盘边长
int n;
public double knightProbability(int n, int k, int row, int column) {
//创建dp数组
dp = new double[n][n][k+1];
//边长
this.n = n;
return dfs(row, column, k);
}
public double dfs(int i, int j, int k){
//越界
if(i < 0 || j < 0 || i >= n || j >= n){
return 0;
}
//到头
if(k == 0){
return 1.0;
}
//遍历过
if(dp[i][j][k] != 0){
return dp[i][j][k];
}
//记录概率
double res = 0;
//遍历
for(int[] dir: dirs){
res += dfs(i+dir[0], j+dir[1], k-1) / 8.0;
}
//记录
dp[i][j][k] = res;
return res;
}
}
题目来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/knight-probability-in-chessboard