688. 骑士在棋盘上的概率(dfs)

459 阅读1分钟

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

每日刷题第49天 2021.02.18

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

总结与拓展

dfs(每次都存在返回值情况,非全局变量)

  • 非全局变量需要注意点:每一层dfs遍历完成后记得返回return,此时不仅仅只在终止条件的地方进行返回return。
  • (非全局变量)同类型dfs题目1706.球会落何处非常巧妙的练习题,巩固dfs,加深印象
    • 球不能落到下一层的几种情况:
      1. 两个夹角,无法进入到下一层
      2. 贴左边/右边的墙也无法进入下一层

解法

  • 根据题目可知,骑士可以走的路有8条,直到其走了k步或者离开了棋盘。计算骑士仍然留在棋盘上的概率。
  • 分析:对于骑士走的每一步都需要遍历8个方向,为了便于后续对骑士移动的位置进行处理,将8种情况的x和y的变量分别存储到两个数组中,如下:
    • let dx = [-2,-2,-1,1,2,2,-1,1];
    • let dy = [-1,1,2,2,-1,1,-2,-2];
  • 此时已经解决了骑士走8个方式会到达的位置的单元格下标,那么就需要根据题目所给的k步,来判断骑士当前的状态,是存在单元格上还是已经跳出棋盘。
  • k作为在dfs函数的参数进行传递,这样就可以在每深入一层的时候,k--,直到k == 0那么表示已经递到最深层,可以往回返回了。
  • 那么还差一个?如何计算停留在棋盘上的概率呢?
    • 令递到最深处的点的概率为1,往上返回一次,则将其值* 1 / 8
    • 每个节点停留在棋盘上的概率,就等于:其能够递归下去的一层的子节点停留在棋盘上的概率之和。
  • 剪枝操作
    • 递归的剪枝,通常都是将已经遍历过的节点获取到的概率值,存储下来,再下次进行遍历的时候,直接取出来返回即可。优化了相同节点的递归操作。
/**
 * @param {number} n
 * @param {number} k
 * @param {number} row
 * @param {number} column
 * @return {number}
 */
var knightProbability = function(n, k, row, column) {
  // 记录每个点的概率是必须的,避免走重复的路,优化
  // 8个方向,数组,判断是否出棋盘,没有出棋盘的,给当前还在棋盘上的总数++
  // 不需要判断是否走过,只需要取当前的概率进行相乘即可
  // 栈中添加,没有走出棋盘的位置,进行下一次的搜索
  if(k == 0) return 1;
  // 优化放在最后再写
  // let step = new Array(n);
  // for(let i = 0; i < n; i++) {
  //   step[i] = new Array(n).fill(1);
  // }
  // 声明三位数组
  let ans = new Array(n);
  for(let i = 0; i < n; i++) {
    ans[i] = new Array(n);
  }
  for(let i = 0; i < n; i++){
    for(let j = 0; j < n; j++){
      ans[i][j] = new Array(101).fill(0);
    }
  }
  // console.log('lal',ans[0][0][2]);
  // 问题:怎么知道最终的总概率呢? K
  // let stack = [[row, column]];
  let dx = [-2,-2,-1,1,2,2,-1,1];
  let dy = [-1,1,2,2,-1,1,-2,-2];
  function dfs(x,y,step) {
    // 当需要走的步数为0时,表示已经到最后的位置
    if(step == 0) return 1;
    if(ans[x][y][step] != 0) return ans[x][y][step];
    // 当前已经遍历过的就不要再遍历了
    // if(ans[x][y][step]){
    //   return ans[x][y][step];
    // }
    for(let i = 0; i < 8; i++){
      // 遍历当前节点的8个方向
      let ndx = x + dx[i];
      let ndy = y + dy[i];
      // 判断当前的数是否合理
      if(ndx >= 0 && ndx < n && ndy >= 0 && ndy < n){
        ans[x][y][step] += (0.125 * dfs(ndx,ndy,step - 1));
        // console.log(x,y,step,'对应',ans[x][y][step]);
      }
    }
    return ans[x][y][step];
    // console.log(ans[x][y][step]);
  }
  // dfs来进行计算概率
  dfs(row, column,k);
  return ans[row][column][k];
};