问题描述:
已知一个
N
x
N
的国际象棋棋盘,棋盘的行号和列号都是从0开始。即最左上角的格子记为
(0, 0)
, 最右下角的记为
(N-1, N-1)
。
现有一个“马”(也译作“骑士”)位于
(r, c)
,并打算进行
K
次移动。
如下图所示,国际象棋的“马”每一步先沿水平或垂直方向移动2个格子,然后向与之相垂直的方向再移动1个格子,共有8个可选的位置。
现在“马”每一步都从可选的位置(包括棋盘外部的)中独立随机地选择一个进行移动,直到移动了 K 次或跳到了棋盘外面。
求移动结束后,“马”仍留在棋盘上的概率。
问题分析:
这个是今天同学阿里内推笔试题目,我拿到题目的第一眼望去,就是深度优先搜索,回溯法,真是太年轻了,这个用回溯法的话,复杂度会非常高,因为路径应该是可以重复走的,可能性路径就更多了。后天面试官提示是动态规划,才开始考虑k这个因素。
动态规划解法:
- 令
f[r][c][steps]
表示, 经过steps
步之后落在棋盘(r, c)
上的概率。则dp
递推方程如下:
dr, dc
表示下一步要走的偏量,且r = ri+dr
,c = cj+dc
,也就是,只要在steps-1的步骤中,所有可能的位置到达steps步骤中的(r, c)
,的概率总和,就是steps步骤中(r, c)
的概率。最后只要求出在最后一步棋盘上所有位置上概率的总和就是题目要求的结果。 - 问题:为什么要除以8?因为每次有八分之一的概率选择一个方向,也就是每个位置被选择的概率是1/8。
- 问题:出界的概率怎么办?这个不用考虑,因为,只求界内的概率,所以不用考虑出界的概率。
- 现在看看优化问题,很显然,在每一个steps步骤中,只需要当前步骤steps的数据,和前一个steps-1的数据,就可以完成计算,所以
f[r][c][steps]
这个3维数组,完全可以用两个2维数组代替。
所以用,dp2
表示f[][][steps]
,用dp
表示f[][][steps-1]
。
Python3实现
# @Time :2018/7/21
# @Author :LiuYinxing
# 时间复杂度 O(N^2K) 空间复杂度 O(N^2)
class Solution:
def knightProbability(self, N, K, r, c):
dp = [[0] * N for _ in range(N)] # 初始化dp
dp[r][c] = 1
for _ in range(K):
dp2 = [[0] * N for _ in range(N)] # 初始化当前dp
for r in range(N):
for c in range(N):
for dr, dc in zip((2, 2, -2, -2, 1, 1, -1, -1), (1, -1, 1, -1, 2, -2, 2, -2)): # 八个方向
if 0 <= r + dr < N and 0 <= c + dc < N: # 判断是否出界
dp2[r+dr][c+dc] += dp[r][c] / 8.0 # 保留棋盘内的概率(除以8,是因为有八个方向,每个方向是八分之一)
dp = dp2 # 更新steps-1步骤中的棋盘概率数据
return sum(map(sum, dp)) # 把落在棋盘上的所有位置的概率加起来,就是最后落在棋盘上的概率
if __name__ == '__main__':
solu = Solution()
print(solu.knightProbability(N=3, K=2, r=0, c=0))
欢迎指正哦。
[1]参考文献: https://leetcode.com/problems/knight-probability-in-chessboard/solution/
声明:第一次用markdown写公式,费劲死了,禁止转载。