当前位置:   article > 正文

Leetcode688: 骑士在棋盘上的概率(medium, 基于概率转移矩阵的解法)_骑士棋盘概率转移矩阵

骑士棋盘概率转移矩阵

目录

1. 题目描述

2. 解题分析

2.1 概率转移矩阵

2.2 概率状态转移

2.3 处理流程

3. 代码实现

4. 进一步的优化策略


 

1. 题目描述

        参见Leetcode688: 骑士在棋盘上的概率(medium)https://blog.csdn.net/chenxy_bwave/article/details/122975361

2. 解题分析

        在上一篇中给出的是基于动态规划的解决方案。本篇给出从另外一个有趣的角度出发解决方案。

2.1 概率转移矩阵

        对于棋盘上的每一格格子,反过来看,有多个可能的位置可能通过走一步到达。以n=3的棋盘为例,如下所示(图中的数字表示cell的标号,是为了方便说明):

d6b8fa28d39f4d7092fde2d2d527b8a0.png

图1 棋盘示例 

        从cell#2和cell#7能够到达cell#0;从cell#6和cell#8能够到达cell#1,。。。,余者类推。

        根据题意,从每个cell出发向任意8各方向移动的概率相等,即每个方向都是1/8。由此我们可以得到如下图所示的概率转移矩阵(这个矩阵的大小是gif.latex?n%5E2%20%5Ctimes%20n%5E2):

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA56yo54mb5oWi6ICV,size_20,color_FFFFFF,t_70,g_se,x_16

图2 概率转移矩阵示例 

        其中, 行表示出发cell,列表示到达cell。每个格子中的数值表示从所在行对应的cell到达列对应的cell的概率。图中数值实际上应该是1/8。写成1只是处于方便,在外面统一乘以一个1/8的因子即可。Unsurprisingly, 这个矩阵是一个对称矩阵,更甚的是,它沿对角线和反对角线都是对称的。

         进一步,用一个长度为gif.latex?n%5E2行向量p来表示棋盘的概率状态,向量每个元素值表示棋子出现在对应cell中的概率。该行向量中的元素序号与图1中所示的cell标号对应。比如说,p[0]表示棋子当前出现在棋盘最左上cell中的概率。

2.2 概率状态转移

        基于以上表示,从棋盘的一个状态出发经过一步以后到达的下一个状态就可以用一个矩阵乘法来表示,如下所示(其中T表示概率转移矩阵):

                        gif.latex?p%5Bt%5D%20%3D%20p%5Bt-1%5D%20T

        进一步,从某个初始状态gif.latex?p_0出发,经过k步移动以后所得到的概率状态(记为gif.latex?p_k)为:

                        gif.latex?p_k%20%3D%20p_%7Bk-1%7D%5Ccdot%20T%20%3D%20p_%7Bk-2%7D%5Ccdot%20T%20%5Ccdot%20T%20%3D%20%5Ccdots%20%3D%20p_0%20%5Ccdot%20T%5Ek 

        就是一个矩阵的幂运算,然后左乘一个行向量就搞定了。非常漂亮,是不是?熟悉概率论和随机过程的小伙伴对此当然会微微一笑,这就是大名鼎鼎的马尔科夫过程的状态转移动力学机制(dynamics)。

2.3 处理流程

        基于以上讨论,本问题的解决流程如下:

  1.         根据输入n,构建概率转移矩阵T
  2.         根据输入n和row, col 构建初始概率状态向量gif.latex?p_0
  3.         计算gif.latex?p_k%20%3D%20p_0%20%5Ccdot%20T%5Ek 
  4.         计算经过k步以后仍然在棋盘上的概率,就是对gif.latex?p_k中各项求和

        这里唯一需要担心的是,矩阵幂运算的效率如何,尤其是在n比较大的时候。以下实现中采用numpy来实现矩阵乘法。

3. 代码实现

  1. import time
  2. import numpy as np
  3. class Solution:
  4. def knightProbability_matpower(self, n: int, k: int, row: int, col: int) -> float:
  5. delta = [(1,2),(2,1),(2,-1),(1,-2),(-1,-2),(-2,-1),(-2,1),(-1,2)]
  6. # 1. Create row vector to represent prob state
  7. p0 = np.zeros((1,n*n))
  8. p0[0][row*n+col] = 1
  9. # 2. Create prob transition matrix
  10. T = np.zeros((n*n, n*n))
  11. for l in range(n*n): # for each row of T. Transition from cell#l
  12. # Fill T according to the reachability from cell#r
  13. l_row = l // n
  14. l_col = l % n
  15. for m in range(8):
  16. r_new,c_new = l_row + delta[m][0], l_col + delta[m][1]
  17. if r_new>=0 and r_new<n and c_new>=0 and c_new<n:
  18. T[l][r_new * n + c_new] = 1
  19. T /= 8
  20. # print(T,p0)
  21. # 3. Calculate the final prob state after k steps
  22. Tk = np.eye(n*n)
  23. for j in range(0,k):
  24. Tk = Tk @ T
  25. pk = p0 @ Tk
  26. # 4. Calculate the probability inside the board after k steps
  27. return np.sum(pk,1)[0]

        运行结果(及对比):

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA56yo54mb5oWi6ICV,size_20,color_FFFFFF,t_70,g_se,x_16

        跟之前用动态规划的方法相对比:在k较小的情况下如{6,12},{10,13}的基于概率转移矩阵幂的解法更快,而k比较大的时候如{8,30}则概率转移矩阵幂要慢得多。BUT。。。

4. 进一步的优化策略

        如上一节所示,在步数较大的时候,基于概率转移矩阵幂的方案比较慢。但是那是因为我们还有杀招没有使出来。

        由于概率转移矩阵T是实对称方阵,由线性代数可知,存在正交矩阵P使得gif.latex?T%20%3D%20P%5E%7B-1%7DD_T%20P

成立。其中gif.latex?D_T为对角线矩阵,其对角线元素为T的特征根,可以表示为gif.latex?D_T%3Ddiag%28%5Clambda_0%2C%5Clambda_1%2C...%29.

        由此可得:

                gif.latex?%5Cbegin%7Balign%7D%20p_k%20%26%3D%20p_0%20%5Ccdot%20T%5Ek%20%5C%5C%20%26%3D%20p_0%20%5Ccdot%20%28P%5E%7B-1%7DD_TP%29%5Ek%20%5C%5C%20%26%3D%20p_0%5Ccdot%20P%5E%7B-1%7DD_T%5Ek%20P%20%5C%5C%20%26%3D%20p_0%20%5Ccdot%20P%5E%7B-1%7D%20diag%28%5Clambda_0%5Ek%2C%5Clambda_1%5Ek%2C...%29%20P%20%5Cend%7Balign%7D 

         只要先对T进行正交对角化处理,T的k次幂缩减为只需要1次矩阵运算,以及若干个实数(T的特征根)的k次幂运算。由于T的对角化需要一些一次性的代价,所以在k较小时可能没有太大的收益,但是随着k越来越大,所带来的计算量节约将越来越大!

        这就是数学的力量!

        关于这一方案的代码以后再补充。

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/271699
推荐阅读
相关标签
  

闽ICP备14008679号