Knight On the Chessboard

Leetcode 688: 骑士在棋盘上的概率

Definition:

在这里插入图片描述

思路

由于刷题尹始对DP和DFS之类的问题还没有什么概念,看到题目第一想法就是直接正向求解,在使用递归实现基本问题的求解之后发现对于k较大的情形递归的方法就失效了,直接超时。后来在评论里面也看到不少老哥用递归超时的,其实思路很简单,但是就是无法AC。
对于每一个起始位置,我们可以通过计算在下一步有多少种可能的走法,找出下一步所有可能的位置以及对应的概率之后,不断向后迭代,最终只留下了能够不离开棋盘的所有步数对应的概率,求和即可得到最终的结果,代码如下

class Solution:
    def possible_position(self, n: int, row: int, col:int):
        positions = []
        if row - 2 >= 0:
            if col - 1 >= 0:
                positions.append((row - 2, col - 1))
            if col + 1 < n:
                positions.append((row - 2, col + 1))

        if row - 1 >= 0:
            if col - 2 >= 0:
                positions.append((row - 1, col - 2))
            if col + 2 < n:
                positions.append((row - 1, col + 2))

        if row + 1 < n:
            if col - 2 >= 0:
                positions.append((row + 1, col - 2))
            if col + 2 < n:
                positions.append((row + 1, col + 2))

        if row + 2 < n:
            if col - 1 >= 0:
                positions.append((row + 2, col - 1))
            if col + 1 < n:
                positions.append((row + 2, col + 1))

        return positions

    def knightProbability(self, n: int, k: int, row: int, column: int) -> float:
        possible_pos = self.possible_position(n, row, column)
        possibility = len(possible_pos) / 8

        if k == 0 and (0 <= row < n) and (0 <= column < n):
            return 1

        if k == 1:
            return possibility

        candidates = []
        for pos in possible_pos:
            if not ((0 <= row < n) or (0 <= column < n)):
                continue
            candidates.append(possibility * (1 / len(possible_pos)) * self.knightProbability(n, k - 1, pos[0], pos[1]))
        return sum(candidates)
        

以上是使用递归进行求解的代码实现,该方法在实际运行时存在超时的问题。在阅读官方题解之后,了解了什么是动态规划并对官方给出的代码进行了理解,下面是其给出的解题方法:

class Solution:
    def knightProbability(self, n: int, k: int, row: int, column: int) -> float:
        dp = [[[0] * n for _ in range(n)] for _ in range(k + 1)]
        for step in range(k + 1):
            for i in range(n):
                for j in range(n):
                    if step == 0:
                        dp[step][i][j] = 1
                    else:
                        for di, dj in ((-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2), (2, -1), (2, 1)):
                            ni, nj = i + di, j + dj
                            if 0 <= ni < n and 0 <= nj < n:
                                dp[step][i][j] += dp[step - 1][ni][nj] / 8
        return dp[k][row][column]

可以看到,使用动态规划的方法思路非常简洁。使用一个三维数组,第一个维度是channel(也就是step)维度,另外两个维度对应棋盘的行和列。这里其实使用了逆向思维,因为正向推理的过程中可能性非常多,但是反过来考虑的话,如果到了最后一步(k=0)骑士仍旧在棋盘上(此时概率为1,因为没有了后续步骤,骑士已经固定在棋盘上),那么就可以推理在k=1时其在棋盘上的可能位置并计算在对应位置的概率(例如在k=0时骑士在棋盘上的 ( r o w , c o l u m n ) (row,column) (row,column)位置,那么在上一步中其可能存在的位置是可以计算的,即 d p [ s t e p − 1 ] [ r o w + d i ] [ c o l u m n + d j ] dp[step - 1][row + di][column + dj] dp[step1][row+di][column+dj]),通过反向求解概率直到求得step=k时,便可获得最终结果。

题后反思

本题在题目设置上的技巧在于棋盘格的大小n是固定的,这也就决定了在使用动态规划时状态变量的个数是固定的,因此通过这种反向求解的思路效率较高。值得一提 的是,由于当前的状态仅仅与上一步状态有关,因此状态数组的实际大小可以设置为size = (2, n, n) 通过滚动迭代的方式进一步节省空间开销。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值