开启我的LeetCode刷题日记:688. 骑士在棋盘上的概率

135 阅读2分钟

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

编程世界总是离不了算法

最近在看框架源码时,会有很多算法的实现逻辑,有时候会感到吃力

于是决定蹭着假期,加强算法和数据结构相关的知识

那怎么提升呢?

其实我知道算法这东西没有捷径,多写多练才能提升,于是我开启我的LeetCode刷题之旅

第一阶段目标是:200道,每天12

为了不乱,本系列文章目录分为三部分:

  1. 今日题目:xxx
  2. 我的思路
  3. 代码实现

今天题目:688. 骑士在棋盘上的概率

在一个 n x n 的国际象棋棋盘上,一个骑士从单元格 (row, column) 开始,并尝试进行 k 次移动。行和列是 从 0 开始 的,所以左上单元格是 (0,0) ,右下单元格是 (n - 1, n - 1) 。

象棋骑士有8种可能的走法,如下图所示。每次移动在基本方向上是两个单元格,然后在正交方向上是一个单元格。

每次骑士要移动时,它都会随机从8种可能的移动中选择一种(即使棋子会离开棋盘),然后移动到那里。

骑士继续移动,直到它走了 k 步或离开了棋盘。

返回 骑士在棋盘停止移动后仍留在棋盘上的概率 。

 

示例 1:

输入: n = 3, k = 2, row = 0, column = 0 输出: 0.0625 解释: 有两步(到(1,2),(2,1))可以让骑士留在棋盘上。 在每一个位置上,也有两种移动可以让骑士留在棋盘上。 骑士留在棋盘上的总概率是0.0625。 示例 2:

输入: n = 1, k = 0, row = 0, column = 0 输出: 1.00000  

提示:

1 <= n <= 25 0 <= k <= 100 0 <= row, column <= n

我的思路

每次有八个移动位置,问最后在棋盘里面的概率? 必定需要穷举。即,递归。使用记忆化来进行优化,也即是 动态规划问题。

定义一个dp函数:目标是求,在当前位置(i,j)移动 k 次还在棋盘的概率。

base case的确定: 越界,return 0;k次移动完且没有越界,return 1

根据函数定义,来确定状态(「状态」必须是独一无二的)。 结合函数定义,当前这道题目独一无二的状态就是,(i,j,k)

确定每个状态的选择: 每个状态的选择就是往八个方向移动:

函数定义。
dp(i, j, k)函数:在当前位置(i,j)还可以移动 k 次还在棋盘的概率为dp(i, j, k)

代码实现

/**
 * @param {number} n
 * @param {number} k
 * @param {number} row
 * @param {number} column
 * @return {number}
 */
// 移动一次
// m , n
// m-2 n + 1 / n - 1
// m-1 n + 2 / n - 2

// m+2 n + 1 / n - 1
// m+1 n + 2 / n - 2

var knightProbability = function(n, k, row, column) {
    const dirs = [[-2, 1], [-2, -1], [2, 1], [2, -1], [-1, 2], [-1, -2], [1, 2], [1, -2]]
    const memo = new Map()

    // 确定「状态」,当前这道题目独一无二的状态就是,(i,j,k)
    // 每个状态的选择:[[-2, 1], [-2, -1], [2, 1], [2, -1], [-1, 2], [-1, -2], [1, 2], [1, -2]] 八个方向
    // 函数dp(i,j,k)在当前位置(i,j)移动 k 次还在棋盘的概率
    const dp = (i, j, k) => {
        // base case: 1、越界了,该位置在贡献 0
        if(i < 0 || i >= n || j < 0 || j >= n){
            return 0
        }
        // 2、走完了且没有越界,该位置在贡献 1
        if(k == 0){
            return 1
        }

        /** 前序遍历位置:进入当前状态前, 查看备忘录 */
        const key = i + ',' + j + ',' +  k     // 当前层「状态」
        let curP = 0                           // 当前层「最优子结构」
        if(memo.has(key)){
            return memo.get(key)
        }

        // 递归:相当于一个多叉树遍历
        for(let dir of dirs){
            curP += dp(i + dir[0], j + dir[1], k - 1)/8
        }

        /** 后序遍历位置:记录当前状态「最优子结构」 &&  返回给上层 */
        memo.set(key, curP)
        return curP
    }

    return dp(row, column, k)
};


总结

实现方式其实有很多,这里仅供参考~

由于刚开始刷题,也不知道从哪里刷好,如果前辈们有好的建议,希望不吝赐教,感谢🌹