题目地址:
https://leetcode.com/problems/out-of-boundary-paths/
给定一个 m × n m\times n m×n的网格,在 ( i , j ) (i,j) (i,j)这个位置有个小球,其可以沿四个方向每步走一格。允许其走 N N N步,问其走出界的所有可能路径的路径总数是多少。注意,一旦走出界,之后再走的路径不作区分。例如 1 × 1 1\times 1 1×1的网格,小球在 ( 0 , 0 ) (0,0) (0,0)这个位置,则对任意 N ≥ 1 N\ge 1 N≥1,答案都是 4 4 4。最后答案要模 1 0 9 + 7 10^9+7 109+7之后返回。
记忆化搜索方法参考https://blog.csdn.net/qq_46105170/article/details/109181274。下面介绍动态规划方法:
设 f [ s ] [ i ] [ j ] f[s][i][j] f[s][i][j]是从 ( i , j ) (i,j) (i,j)出发,恰好走 s s s步能走出界的路径总数。那么初始条件 f [ 1 ] [ i ] [ j ] f[1][i][j] f[1][i][j]是一步出界的路径总数,很容易计算的。如果总共要走 s s s步,则可以按照第一步走到哪儿做划分,则有: f [ s ] [ i ] [ j ] = ∑ ( i , j ) → ( x , y ) f [ s − 1 ] [ x ] [ y ] f[s][i][j]=\sum_{(i,j)\to (x,y)} f[s-1][x][y] f[s][i][j]=(i,j)→(x,y)∑f[s−1][x][y]注意,这里的 ( x , y ) (x,y) (x,y)一定要在界内,因为 f f f的定义是“恰好”走 s s s步能走出界的路径总数。最后答案就是要求 ∑ k = 1 N f [ k ] [ i ] [ j ] \sum_{k=1}^{N}f[k][i][j] k=1∑Nf[k][i][j]代码如下:
public class Solution {
public int findPaths(int m, int n, int N, int i, int j) {
int MOD = (int) (1E9 + 7);
// dp[k][i][j]是从(i, j)这个位置恰好走k步出界的路径数
int[][][] dp = new int[N + 1][m][n];
int[][] dirs = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
for (int step = 1; step <= N; step++) {
for (int x = 0; x < m; x++) {
for (int y = 0; y < n; y++) {
if (step == 1) {
for (int[] dir : dirs) {
int nextX = x + dir[0], nextY = y + dir[1];
if (!inBound(nextX, nextY, m, n)) {
dp[step][x][y]++;
}
}
} else {
for (int[] dir : dirs) {
int nextX = x + dir[0], nextY = y + dir[1];
if (inBound(nextX, nextY, m, n)) {
dp[step][x][y] += dp[step - 1][nextX][nextY];
dp[step][x][y] %= MOD;
}
}
}
}
}
}
int res = 0;
for (int k = 0; k <= N; k++) {
res = (res + dp[k][i][j]) % MOD;
}
return res;
}
private boolean inBound(int x, int y, int m, int n) {
return 0 <= x && x < m && 0 <= y && y < n;
}
}
时间复杂度 O ( N m n ) O(Nmn) O(Nmn)。
可以用滚动数组做空间优化。优化的时候需要注意两点:
1、计算dp[step & 1][x][y]
之前一定要清零,把上上层的值清掉,否则会得出错误答案;
2、每次计算完一层,就要将dp[step & 1][i][j]
累加到答案里,由于滚动数组只保存两层数据,所以不能像上面的方法一样到最后再累加。
代码如下:
public class Solution {
public int findPaths(int m, int n, int N, int i, int j) {
int MOD = (int) (1E9 + 7);
// dp[k][i][j]是从(i, j)这个位置恰好走k步出界的路径数
int[][][] dp = new int[2][m][n];
int[][] dirs = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
int res = 0;
for (int step = 1; step <= N; step++) {
for (int x = 0; x < m; x++) {
for (int y = 0; y < n; y++) {
// 计算前先清零
dp[step & 1][x][y] = 0;
if (step == 1) {
for (int[] dir : dirs) {
int nextX = x + dir[0], nextY = y + dir[1];
if (!inBound(nextX, nextY, m, n)) {
dp[step][x][y]++;
}
}
} else {
for (int[] dir : dirs) {
int nextX = x + dir[0], nextY = y + dir[1];
if (inBound(nextX, nextY, m, n)) {
dp[step & 1][x][y] += dp[step - 1 & 1][nextX][nextY];
dp[step & 1][x][y] %= MOD;
}
}
}
}
}
// 一旦算完一层,立刻累加
res = (res + dp[step & 1][i][j]) % MOD;
}
return res;
}
private boolean inBound(int x, int y, int m, int n) {
return 0 <= x && x < m && 0 <= y && y < n;
}
}
时间复杂度不变,空间 O ( m n ) O(mn) O(mn)。
当然,状态的设置不是唯一的。也可以简单的设 f [ s ] [ i ] [ j ] f[s][i][j] f[s][i][j]就是从 ( i , j ) (i,j) (i,j)出发走 s s s步走出界的路径总数,那么可以按照下一步走到哪儿做分类,则有: f [ s ] [ i ] [ j ] = ∑ ( i , j ) → ( x , y ) f [ s − 1 ] [ x ] [ y ] f[s][i][j]=\sum_{(i,j)\to (x,y)} f[s-1][x][y] f[s][i][j]=(i,j)→(x,y)∑f[s−1][x][y]如果 ( x , y ) (x,y) (x,y)出界的话 f f f就取 1 1 1。最后返回 f [ N ] [ i ] [ j ] f[N][i][j] f[N][i][j]即可。下面直接给出滚动数组优化后的代码:
public class Solution {
public int findPaths(int m, int n, int N, int i, int j) {
int MOD = (int) (1E9 + 7);
// dp[s][i][j]表示从(i, j)走s步走出界的路径总数
int[][][] dp = new int[2][m][n];
int[] dir = {1, 0, -1, 0, 1};
for (int k = 1; k <= N; k++) {
for (int x = 0; x < m; x++) {
for (int y = 0; y < n; y++) {
// 先清零
dp[k & 1][x][y] = 0;
for (int p = 0; p < 4; p++) {
int nextX = x + dir[p], nextY = y + dir[p + 1];
dp[k & 1][x][y] += inBound(nextX, nextY, m, n) ? dp[k - 1 & 1][nextX][nextY] : 1;
dp[k & 1][x][y] %= MOD;
}
}
}
}
return dp[N & 1][i][j];
}
private boolean inBound(int x, int y, int m, int n) {
return 0 <= x && x < m && 0 <= y && y < n;
}
}
时间复杂度一样,空间 O ( m n ) O(mn) O(mn)。