LeetCode 688. 骑士在棋盘上的概率题解

688. 骑士在棋盘上的概率题解

题目来源:688. 骑士在棋盘上的概率

2022.02.17 每日一题

每日一题专栏地址:LeetCode 每日一题题解更新中

题目大意是判断国际象棋中的骑士从初始坐标 ( r o w , c o l u m n ) (row,column) (row,column)开始在 k k k步之后能不能走出棋盘

本题计算概率的方法是:分子是你到K步为止,总共有多少种情况,分母是总共的步数,K为1是8次,K为2是64次。即为 p o w ( 8 , k ) pow(8,k) pow(8,k)

我首先想到的方法是直接进行dfs搜索,但是最后直接 TLE 了,( dfs 超时代码放在文章最后了)

法一:记忆深搜

然后通过分析发现,在递归过程中会有很多重复的步骤

例如:首次选择了 ( 1 , 2 ) (1,2) (12)方向,下一跳选择了 ( − 1 , − 2 ) (-1,-2) (12)方向,就回到了原地,就造成了重复,因此可以使用一个数组来记录已经走过的点,来减少时间

class Solution {
public:
    int n;
    vector<vector<int>> dirs = {{-1, -2}, {-2, -1}, {-2, 1}, {-1, 2}, {1, 2}, {2, 1}, {2, -1}, {1, -2}};
    vector<vector<vector<double>>> dp;

    double knightProbability(int n, int k, int row, int column) {
        // 创建一个缓存数组,用来存储已经递归过的值
        dp = vector(n, vector(n, vector(k + 1, 0.0)));
        this->n = n;
        return dfs(row, column, k);
    }

    double dfs(int i, int j, int times) {
        // 如果骑士已经不在棋盘上,则骑士在棋盘上面的概率为 0
        if (i < 0 || j < 0 || i >= n || j >= n)
            return 0;

        // 这是骑士一定在棋盘上,但是移动次数已经没有了,、
        // 此时骑士一定在棋盘上,返回 1
        if (times == 0)
            return 1;

        // 如果此时的点已经被递归过了,则可以直接返回原来递归的值,免于重复递归,浪费时间
        if (dp[i][j][times] != 0.0)
            return dp[i][j][times];

        // 创建一个 temp 记录此时递归的值
        double temp = 0.0;
        for (vector<int> dir: dirs) {
            // 获取下一跳的坐标
            int x = i + dir[0], y = j + dir[1];
            // 进行下一次递归,并且得到对应的结果
            temp += dfs(x, y, times - 1) / 8;
        }

        // 将temp的值赋给 cache[i][j][times]
        dp[i][j][times] = temp;
        return temp;
    }
};
class Solution {
    int n;
    int[][] dirs = {{-1, -2}, {-2, -1}, {-2, 1}, {-1, 2}, {1, 2}, {2, 1}, {2, -1}, {1, -2}};
    double[][][] dp;

    public double knightProbability(int n, int k, int row, int column) {
        // 创建一个缓存数组,用来存储已经递归过的值
        dp = new double[n][n][k + 1];
        this.n = n;
        return dfs(row, column, k);
    }

    double dfs(int i, int j, int times) {
        // 如果骑士已经不在棋盘上,则骑士在棋盘上面的概率为 0
        if (i < 0 || j < 0 || i >= n || j >= n) return 0;

        // 这是骑士一定在棋盘上,但是移动次数已经没有了,、
        // 此时骑士一定在棋盘上,返回 1
        if (times == 0) return 1;

        // 如果此时的点已经被递归过了,则可以直接返回原来递归的值,免于重复递归,浪费时间
        if (dp[i][j][times] != 0.0) return dp[i][j][times];

        // 创建一个 temp 记录此时递归的值
        double temp = 0.0;
        for (int[] dir : dirs) {
            // 获取下一跳的坐标
            int x = i + dir[0], y = j + dir[1];
            // 进行下一次递归,并且得到对应的结果
            temp += dfs(x, y, times - 1) / 8;
        }

        // 将temp的值赋给 cache[i][j][times]
        dp[i][j][times] = temp;
        return temp;
    }
}
  • 时间复杂度 O ( k n 2 ) O(kn^2) O(kn2)
  • 空间复杂度 O ( k n 2 ) O(kn^2) O(kn2)
法二:动态规划

有了记忆化搜索,那也少不了dp了。

想知道第 k 步有多少的概率,可以从第 k - 1 步开始推算

根据本题目,我们知道一个骑士有 8 个方向的选择,每个方向的选择概率是相等的,因此每个方向被选中的概率都是 1 8 \frac{1}{8} 81,因此第 k 步就是 8 个方向的每个 1 8 \frac{1}{8} 81的概率进行相加

class Solution {
public:
    int n;
    vector<vector<int>> dirs = {{-1, -2}, {-2, -1}, {-2, 1}, {-1, 2}, {1, 2}, {2, 1}, {2, -1}, {1, -2}};

    double knightProbability(int n, int k, int row, int column) {
        // 给动态规划开辟空间
        vector<vector<vector<double>>> dp = vector(k + 1, vector(n, vector(n, 0.0)));
        for (int l = 0; l <= k; l++) {
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    // 如果 l (k值)等于 0 ,其概率为 1 
                    if (l == 0) {
                        dp[l][i][j] = 1;
                    } else {
                        for (vector<int> dir: dirs) {
                            int x = i + dir[0], y = j + dir[1];
                            if (x >= 0 && y >= 0 && x < n && y < n)
                                dp[l][i][j] += dp[l - 1][x][y] / 8;
                        }
                    }
                }
            }
        }
        return dp[k][row][column];
    }

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

    public double knightProbability(int n, int k, int row, int column) {
        // 给动态规划开辟空间
        double[][][] dp = new double[k + 1][n][n];
        for (int l = 0; l <= k; l++) {
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    // 如果 l (k值)等于 0 ,其概率为 1 
                    if (l == 0) {
                        dp[l][i][j] = 1;
                    } else {
                        for (int[] dir : dirs) {
                            int x = i + dir[0], y = j + dir[1];
                            if (x >= 0 && y >= 0 && x < n && y < n) dp[l][i][j] += dp[l - 1][x][y] / 8;
                        }
                    }
                }
            }
        }
        return dp[k][row][column];
    }

}
  • 时间复杂度 O ( k n 2 ) O(kn^2) O(kn2)
  • 空间复杂度 O ( k n 2 ) O(kn^2) O(kn2)

以上方法都要比直接模拟的复杂度 O ( 8 k ) O(8^k) O(8k)小的多

以下是 d f s dfs dfs的超时代码

class Solution {
public:
    // success 代表最后跳出棋盘的个数
    // all - success 代表最后留在棋盘上面的个数
    int sucess = 0, all = 0;
    int n;
    // 八个方向的访问坐标
    vector<vector<int>> dirs = {{-1, -2}, {-2, -1}, {-2, 1}, {-1, 2}, {1, 2}, {2, 1}, {2, -1}, {1, -2}};

    double knightProbability(int n, int k, int row, int column) {
        this->n = n;
        dfs(row, column, k);
        return (all - sucess) * 1.0 / pow(8, k);
    }

    void dfs(int i, int j, int times) {

        // 如果不在棋盘范围内,则证明骑士已经跳出了棋盘
        if (i < 0 || j < 0 || i >= n || j >= n) {
            // 对应值加一,退出递归
            sucess++;
            all++;
            return;
        }
        
        // 如果 times 的次数小于 1 证明是最后一次移动,同时骑士还在棋盘上
        if (times < 1) {
            all++;
            return;
        }
        
        // 遍历方向数组,8 个方向进行下一次递归操作
        for (vector<int> dir: dirs) {
            int x = i + dir[0], y = j + dir[1];
            dfs(x, y, times - 1);
        }
    }
};
class Solution {
    // success 代表最后跳出棋盘的个数
    // all - success 代表最后留在棋盘上面的个数
    int sucess = 0, all = 0;
    int n;
    // 八个方向的访问坐标
    int[][] dirs = {{-1, -2}, {-2, -1}, {-2, 1}, {-1, 2}, {1, 2}, {2, 1}, {2, -1}, {1, -2}};

    double knightProbability(int n, int k, int row, int column) {
        this.n = n;
        dfs(row, column, k);
        return (all - sucess) * 1.0 / Math.pow(8, k);
    }

    void dfs(int i, int j, int times) {

        // 如果不在棋盘范围内,则证明骑士已经跳出了棋盘
        if (i < 0 || j < 0 || i >= n || j >= n) {
            // 对应值加一,退出递归
            sucess++;
            all++;
            return;
        }
        
        // 如果 times 的次数小于 1 证明是最后一次移动,同时骑士还在棋盘上
        if (times < 1) {
            all++;
            return;
        }
        // 遍历方向数组,8 个方向进行下一次递归操作
        for (int[] dir : dirs) {
            int x = i + dir[0], y = j + dir[1];
            dfs(x, y, times - 1);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值