[LC]骑士在棋盘上的概率

185 阅读2分钟

这是我参与2022首次更文挑战的第21天,活动详情查看:2022首次更文挑战

题目

688. 骑士在棋盘上的概率 - 力扣(LeetCode) (leetcode-cn.com)

解析

停止移动后仍在棋盘上的概率,其实可以换个思路:

  • 求至多K步后,走出棋盘的概率。

这里我们由题目可知:

  • 每一次的移动,都是完全随机的。也就是说,每种走法的概率都是1/8。

那么,走出棋盘的情况,就是走到单元格外面了。

一种“想当然的”思路是:

  • 求出所有可能走出棋盘的情况数,和所有的情况数相除。

这种思路是不正确的。

假设在第一步有7种走法走出棋盘,那么,这个走出的概率就至少是7/8,和上面的情况实际上是不符合的。

原因是:每一步对于最终结果的权重,都是不同的。

为每一步加上权重(1/8)。如果当前格子已在棋盘之外,那么对于这一步必然是100%的概率,因此返回1;反之,如果当前走了K步,则返回0。

对于都不在上述情况中的,为每一步加上1/8的权重,递归求取。

得到以下代码:

static int[][] dirs = {{1,2},{2,1},{1,-2},{2,-1},{-1,-2},{-2,-1},{-1,2},{-2,1}};


public double knightProbability(int n, int k, int row, int column) {
    if(row<0 || row>n-1 || column<0 || column>n-1){
        return 0;
    }

    if(k == 0){
        return 1;
    }
    double cur = 0;
    for (int[] dir : dirs) {
        double nxt = knightProbability(n,k-1,row+dir[0],column+dir[1]);
        cur += nxt/8;
    }
    return cur;
}

结果:超时

对于这种DFS的题目,一般来说的优化方式就是记录来方便后续相同的计算不再进行。

对于这个题目,实际上的状态包括:

  • 当前步数K
  • 当前位置row,column。

注意到这里的棋盘是N*N的并且8种走法是对称的,因此对于(k,r,c)和(k,c,r),概率是相同的。

那么此时加上一个三维数组,做一下优化:

double[][][] m = null;

 public double knightProbability(int n, int k, int row, int column) {
        if(m == null){
            m = new double[k+1][n][n];
        }
        //对称性
        if(m[k][row][column]!=0) return m[k][row][column];
        if(m[k][column][row]!=0) return m[k][column][row];
        //...
        //更新
        m[k][row][column] = cur;
     	return cur;
        }

结果:

执行用时:3 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:40.7 MB, 在所有 Java 提交中击败了31.52%的用户

有了这个数组,那么试一试用迭代而非递归,来节省栈(因为这里的k,row,column就记录了所需要的结果),也可以同样地使用对称的性质来节约一般的空间,此处不再给出代码。