前言
最近忙着做毕设实验,之前的周记都搁置了。实验有了些结果,还不错。这周是每日一棋。
本周主题:复杂回溯、记忆化搜索
题目:
- 241204每日一题—Number of Valid Move Combinations On Chessboard
- 241207每日一题—Knight Probability in Chessboard
- 241208每日一题—Transform to Chessboard
Number of Valid Move Combinations On Chessboard
就是说一个move combo就是一个int[][] destinations
,代表每个棋子要移动到的位置,每秒每个棋子移动一个格子,且移动过程中不能发生抢占格子的情况。这个目的地是根据棋子的可移动方向选的,包括原地不动。
DFS+回溯
思路:先预处理每个棋子的所有合法移动。写一个回溯,暴力枚举每个棋子的每个合法移动,如果这些棋子没有重叠在一起,则答案加一。
具体来说, 合法移动包含:
- 棋子的初始位置 $\left(x_0, y_0\right)$ 。
- 棋子的移动方向 $\left(d_x, d_y\right)$ 。
- 棋子的移动次数 step。
在回溯时,可以剪枝:如果当前棋子的当前这个合法移动,与前面的棋子冲突,即同一时刻两个棋子重叠,那么不往下递归,枚举当前棋子的下一个合法移动。
这个回溯的特点是层数很多,把每个节点想象成神经网络的一层,每层的节点数量是合法移动的数量,最终计算是其全连接的结果累加。且在判断是否回溯时,要符合所有先前节点的移动,由于这些移动可能所处的时间步不同,所以需要模拟其发展过程,确认移动过程中不存在冲突即合法。
1 | class Solution { |
Knight Probability in Chessboard
原问题是Knight走$k$步后留在棋盘上的概率,走出一步后,问题变为了Knight走$k-1$步后留在棋盘上的概率,是一个规模更小的子问题,可递归求解。直接递归超时,如下:
1 | // author: SilenceZheng66 |
记忆化搜索
上面的解法有一个问题:递归函数不可记忆,无法进行优化。因此需要重新确定状态和计算方式。
定义状态为 $dfs(k,i,j)$,表示从 $(i,j)$ 出发,走 $k$ 步后仍然在棋盘上的概率。
枚举可走的八个方向, 其中有 $\frac{1}{8}$ 概率走到了 $(x, y)$, 问题变成:
- 从 $(x, y)$ 出发, 走 $k-1$ 步后仍然在棋盘上的概率, 即 $d f s(k-1, x, y)$
八种情况累加,得
递归边界:如果Knight出界, 那么在棋盘上的概率为 0 , 即 $d f s(k, i, j)=0$ 。如果 $k=0$ 时Knight仍然在棋盘上, 那么概率为 1 , 即 $d f s(0, i, j)=1$ 。
递归入口:$d f s(k, r o w, c o l u m n)$, 也就是答案。
三、递归搜索 + 保存递归返回值 = 记忆化搜索
考虑到整个递归过程中有大量重复递归调用(递归入参相同)。由于递归函数没有副作用, 同样的入参无论计算多少次, 算出来的结果都是一样的, 因此可以用记忆化搜索来优化:
- 如果一个状态(递归入参)是第一次遇到,那么可以在返回前,把状态及其结果记到一个 memo 数组中。
- 如果一个状态不是第一次遇到(memo 中保存的结果不等于 memo 的初始值), 那么可以直接返回 memo 中保存的结果。
注意:memo 数组的初始值一定不能等于要记忆化的值。
1 | class Solution { |
Transform to Chessboard
超纲了,火速投降放过自己。
参考文献
[1] https://leetcode.cn/problems/knight-probability-in-chessboard/solutions/2997395/jiao-ni-yi-bu-bu-si-kao-dpcong-ji-yi-hua-dgt6/?envType=daily-question&envId=2024-12-07
[2] https://leetcode.cn/problems/number-of-valid-move-combinations-on-chessboard/solutions/1075478/go-mo-ni-by-endlesscheng-kjpt/?envType=daily-question&envId=2024-12-04
后记
首发于 silencezheng.top,转载请注明出处。