前言
常常DFS爆搜都是会超时的,即使有剪枝的加持,而超时的关键就是很多计算重复了。所以以空间换时间而闻名的动态规划刚好补上这个缺点,用空间把算过的都记下来,方便后面用,只需寻找一下前后递进关系即状态转移即可。
一、骑士在棋盘上的概率
二、DFS与动态规划
1、拥有Kn复杂度的DFS
// 骑士在棋盘上的概率。
// 超时。
public class KnightProbability {
/*
计算骑士走了k步,还在棋盘上的概率。
8个方向当作8个树枝,走一步的成功概率1/8,相同层概率相加,不同层概率相乘。
*/
public double knightProbability(int n, int k, int row, int column) {
// 当不在棋盘上也不可走了。
if (row < 0 || 0 > column || column >= n || n <= row) return 0d;
// 当在棋盘上,但没有步数了,不用继续走了.
if (k == 0) return 1;
//开始往8个方向走。
double rs = 0;
// 左.
rs += knightProbability(n, k - 1, row + 1, column - 2);
rs += knightProbability(n, k - 1, row - 1, column - 2);
// 右。
rs += knightProbability(n, k - 1, row + 1, column + 2);
rs += knightProbability(n, k - 1, row - 1, column + 2);
// 上。
rs += knightProbability(n, k - 1, row - 2, column - 1);
rs += knightProbability(n, k - 1, row - 2, column + 1);
// 下。
rs += knightProbability(n, k - 1, row + 2, column - 1);
rs += knightProbability(n, k - 1, row + 2, column + 1);
// 返回到该层的成功概率积。
return rs / 8;
}
}
2、用空间减少重复计算的动态规划
// dfs超时,采用动规减少重复计算。
class KnightProbability2 {
/*
计算骑士走了k步,还在棋盘上的概率。
走k步,落在棋盘上一格的概率(f[k][i][j]),是从走k-1步(f[k-1][?][?]),加最后从四面八方的格子过来这一步的概率之和。
*/
static int[][] dirs = {{1, -2}, {-1, -2}, {1, 2}, {-1, 2}, {-2, -1}, {-2, 1}, {2, -1}, {2, 1}};
public double knightProbability(int n, int k, int row, int column) {
// 记录走k步,落在在棋盘上(i,j)的概率。
double[][][] f = new double[k + 1][n][n];
// 初始化:走0步,落在棋盘上(i,j)的概率为1.
for (int i = 0; i < n; i++) Arrays.fill(f[0][i], 1);
for (int step = 1; step <= k; step++) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
// 更新状态 f[k][i][j],其为 (i,j) 的四面八方走 1 步过来的概率 * 1/8 的和。
// 8个方向
for (int[] dir : dirs) {
int ni = dir[0] + i, nj = dir[1] + j;
if (ni >= 0 && 0 <= nj && nj < n && n > ni)
f[step][i][j] += f[step - 1][ni][nj] / 8;
}
}
}
}
// 最终结果(逆向思维):
// 棋盘之内的格子,走k步,落到(row,column)的概率。
// 能k步走到(row,column)的,意味者从(row,column)反走k步,能都到棋盘之内的概率。
return f[k][row][column];
}
总结
1)DFS与动态规划,DFS通常用动态规划来解,用空间换时间。