1. 问题描述:
已知一个 NxN 的国际象棋棋盘,棋盘的行号和列号都是从 0 开始。即最左上角的格子记为 (0, 0),最右下角的记为 (N-1, N-1)。 现有一个 “马”(也译作 “骑士”)位于 (r, c) ,并打算进行 K 次移动。 如下图所示,国际象棋的 “马” 每一步先沿水平或垂直方向移动 2 个格子,然后向与之相垂直的方向再移动 1 个格子,共有 8 个可选的位置。现在 “马” 每一步都从可选的位置(包括棋盘外部的)中独立随机地选择一个进行移动,直到移动了 K 次或跳到了棋盘外面。求移动结束后,“马” 仍留在棋盘上的概率。
示例:
输入: 3, 2, 0, 0
输出: 0.0625
解释:
输入的数据依次为 N, K, r, c
第 1 步时,有且只有 2 种走法令 “马” 可以留在棋盘上(跳到(1,2)或(2,1))。对于以上的两种情况,各自在第2步均有且只有2种走法令 “马” 仍然留在棋盘上。
所以 “马” 在结束后仍在棋盘上的概率为 0.0625。
注意:
N 的取值范围为 [1, 25]
K 的取值范围为 [0, 100]
开始时,“马” 总是位于棋盘上
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/knight-probability-in-chessboard
2. 思路分析:
这道题目类似力扣的576题,属于动态规划递推的题目。分析题目可以知道我们由上一个位置走到当前位置的概率为1 / 8(其实在格子中就表示留在棋盘上),所以走到当前这个位置并且留在棋盘上的概率为上一个格子的概率乘以1 / 8。因为涉及到二维平面的横坐标与纵坐标,并且需要知道当前位置跳的步数k所以我们需要声明一个三维数组dp进行状态表示与状态计算,其中dp[i][j][k]表示由上一个位置跳到(i,j)这个位置并且还可以跳K - k步留在棋盘中的概率,所以上一个位置(x, y)到当前的位置(i,j)的概率为八个可能的位置到达当前位置的概率总和。我们实际在递推的时候可以倒着推导,即从其余位置走K步已最终到位置(r,c),所以我们使用三层循环进行状态计算,第一层循环枚举当前还可以走k步,第二层循环枚举棋盘中的横坐标,第三层循环枚举棋盘中的纵坐标,最后一个额外的循环用来枚举上一个位置(x,y)到当前位置(i,j)的八个可能的位置(因为当前k这个状态需要依赖于上一个k + 1的状态所以最外层循环需要先枚举k)。
3. 代码如下:
class Solution:
# 递推即可
def knightProbability(self, n: int, K: int, r: int, c: int) -> float:
# dp[i][j][k]表示当前在位置(i, j)还可以跳K - k步留在棋盘的概率
dp = [[[0] * (K + 1) for i in range(n)] for j in range(n)]
# 初始化边界
for i in range(n):
for j in range(n):
# 初始概率为1, 因为当前是留在棋盘中的
dp[i][j][K] = 1
# 八个方向, 根据题目的图, 马走日
dx = [-2, -1, 1, 2, 2, 1, -1, -2]
dy = [1, 2, 2, 1, -1, -2, -2, -1]
# 逆推所以k是递减的, 并且最外层需要枚举的k这样当前状态依赖于上一个状态k + 1的状态就计算出来了, 递推的结果才是正确的
for k in range(K - 1, -1, -1):
for i in range(n):
for j in range(n):
for u in range(8):
x, y = i + dx[u], j + dy[u]
# 越界了说明概率为0不用管, 不越界的情况下计算上一个位置走一步到当前位置的概率
if 0 <= x < n and 0 <= y < n:
dp[i][j][k] += dp[x][y][k + 1] / 8
return dp[r][c][0]