Leetcode——Knight Probability in Chessboard

来源:互联网 发布:java数组和指针的区别 编辑:程序博客网 时间:2024/06/08 07:42

题目概述

On an NxN chessboard, a knight starts at the r-th row and c-th column and attempts to make exactly K moves. The rows and columns are 0 indexed, so the top-left square is (0, 0), and the bottom-right square is (N-1, N-1).

A chess knight has 8 possible moves it can make, as illustrated below. Each move is two squares in a cardinal direction, then one square in an orthogonal direction.

这里写图片描述

Each time the knight is to move, it chooses one of eight possible moves uniformly at random (even if the piece would go off the chessboard) and moves there.

The knight continues moving until it has made exactly K moves or has moved off the chessboard. Return the probability that the knight remains on the board after it has stopped moving.

Example:Input: 3, 2, 0, 0Output: 0.0625Explanation: There are two moves (to (1,2), (2,1)) that will keep the knight on the board.From each of those positions, there are also two moves that will keep the knight on the board.The total probability the knight stays on the board is 0.0625.

分析

此题是骑士跳的变种问题,即给定了马的起始位置,判断经过K次跳跃后,马还在棋盘上的概率。此题的特点在于,马可以跳到棋盘之外,只要还没完成K次跳跃,其依然可以在棋盘外进行跳跃。

马跳跃的八个方向,可以使用一个二维矩阵来存储,每一个矩阵中存储方向向量的坐标,即

{-1, -2}, {-2, -1}, {-1, 2}, {-2, 1}, {1, -2}, {2, -1}, {1, 2}, {2, 1}

之后便是生成棋盘,并针对棋盘上的某个点进行遍历,判断从棋盘某个点经过下一跳是否还在棋盘上。本质上,此处运用了动态规划的思想,即完整地去考虑所有跳跃的可能,并不断累加符合马在棋盘上的结果。完整实现代码如下:

double knightProbability(int N, int K, int r, int c) {    if (K == 0) return 1; // 不跳跃,则马一定在棋盘上    vector< vector<int> > dirs{ {-1, -2}, {-2, -1}, {-1, 2}, {-2, 1}, {1, -2}, {2, -1}, {1, 2}, {2, 1}}; // 八个方向    vector< vector<double> > dp(N, vector<double>(N, 1));    for (int m = 0; m < K; m++) { // K次跳跃的循环        vector< vector<double> > temp(N, vector<double>(N, 0)); // 生成一张N*N大小的棋盘        for (int i = 0; i < N; i++) {            for (int j = 0; j < N; j++) {                for (auto dir : dirs) { // 遍历棋盘逐个点,判断某一跳之后马是否还在棋盘上                    int x = i + dir[0], y = j + dir[1];                    if (x < 0 || x >= N || y < 0 || y >= N) continue;                    temp[i][j] += dp[x][y];                }            }        }       dp = temp;     }    return dp[r][c] / pow(8, K); //计算概率}

可以发现,上述方法尽管思路比较清晰,但是在时间复杂度上不够理想。思考发现,我们在遍历所有跳跃可能的时候,进行了比较多的重复运算,即可能多次跳跃到了同一个棋盘点上。因此,思考改进方法,引用一个递归迭代方法,即对于已经计算过的点,直接返回其结果即可:

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 r, int c) {        vector<vector<vector<double>>> memo(K + 1, vector<vector<double>>(N, vector<double>(N, 0.0)));        return helper(memo, N, K, r, c) / pow(8, K);    }    double helper(vector<vector<vector<double>>>& memo, int N, int k, int r, int c) {        if (k == 0) return 1.0;        if (memo[k][r][c] != 0.0) return memo[k][r][c]; // 已经计算过该种情况,直接返回,避免重复        for (auto dir : dirs) {            int x = r + dir[0], y = c + dir[1];            if (x < 0 || x >= N || y < 0 || y >= N) continue;            memo[k][r][c] += helper(memo, N, k - 1, x, y); // 查看上一跳        }        return memo[k][r][c];    }

总结

此题的精髓仍然在于动态规划,即遍历方向,判断并累加符合条件的点;而为了避免重复,引入递归方法,可以极大节省时间。

原创粉丝点击