前言
“这是我参与8月更文挑战的第27天,活动详情查看:[8月更文挑战]
出界的路径数
给你一个大小为 m x n 的网格和一个球。球的起始坐标为 [startRow, startColumn] 。你可以将球移到在四个方向上相邻的单元格内(可以穿过网格边界到达网格之外)。你 最多 可以移动 maxMove 次球。
给你五个整数 m、n、maxMove、startRow 以及 startColumn ,找出并返回可以将球移出边界的路径数量。因为答案可能非常大,返回对 109 + 7 取余 后的结果。
示例 1:
输入: m = 2, n = 2, maxMove = 2, startRow = 0, startColumn = 0
输出: 6
示例 2:
输入: m = 1, n = 3, maxMove = 3, startRow = 0, startColumn = 1
输出: 12
提示:
1 <= m, n <= 500 <= maxMove <= 500 <= startRow < m0 <= startColumn < n
思路分析:
1.暴力递归
定义递归函数 dfs(x,y,move) 含义时 返回 从位置(x,y) 出发,最多移动 move 次小球 出界的路径数, 因为在网格中 所以小球只能向 上下左右移动
处理 base case
无效的情况, 移动次数 maxMove 为 0时, 小球还在网格之中, 即 x>-1 && x < m && y > -1 && y < n
有效的情况, 移动次数大于等于 0 时, 小球已经出界 即 x < 0 || x >=m || y < 0 || y>=n
处理完base case 之后, 将位于 (x,y) 的小球 分别向上下左右移动, 递归调用 递归函数 传入新的 坐标的 move 次数(maxMove - 1)
暴力解法超时
2. 记忆化搜索
因为存在重复计算的情况, 添加缓存,优化暴力递归,使用 map 记录
将 移动次数 + 坐标 作为 key, 路径数为 value 返回
key : x + "-" + y + "-" + maxMove
3.动态规划 根据记忆化搜索的 状态 改成 动态规划
dfs(x,y, move) = dfs(x-1, y, move -1) + dfs(x, y + 1, move -1) + dfs(x + 1, y, move -1)+dfs(x, y - 1, move -1)
根据状态转移方程 最大移动步数需要从小到大遍历
定义 dp[move][index] (index = x * n + y)
base case 是网格边缘 移动一次 就可以出界, 那么 移动次数 x > 0 && x <= maxMove 时, 网格边缘的 dp 值 最小为 1 ,注意 边缘的4个角, 他们的出界路径有2种, 所以 他们的 dp 值为 2
AC 代码
记忆化搜索解法
Map<String, Integer> pathMap = new HashMap<>();
public int findPaths(int m, int n, int maxMove, int startRow, int startColumn) {
return dps(m, n, startRow, startColumn, maxMove);
}
int dps(int m, int n, int startRow, int startColumn, int maxMove) {
String key = startRow + "-" + startColumn + "-" + maxMove;
if (pathMap.get(key) != null) {
return pathMap.get(key);
}
// base case
if (maxMove == 0 && startRow >= 0 && startRow < m &&
startColumn >= 0 && startColumn < n) {
pathMap.put(key, 0);
return 0;
}
if (startRow < 0 || startRow >= m ||
startColumn < 0 || startColumn >= n) {
pathMap.put(key, 1);
return 1;
}
int[][] data = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
int ans = 0;
for (int[] arr : data) {
int x = arr[0] + startRow;
int y = arr[1] + startColumn;
if (maxMove > 0) {
ans += dps(m, n, x, y, maxMove - 1);
ans %= 1000000007;
}
}
pathMap.put(key, ans);
return ans;
}
动态规划:
public int findPaths(int m, int n, int maxMove, int startRow, int startColumn) {
int[][] dp = new int[maxMove + 1][m * n];
// base case 注意 4 个 角 重复计算
for (int move = 1; move <= maxMove; move++) {
int i = 0;
int j = 0;
while (j < n) {
dp[move][i * n + j]++;
j++;
}
j = n - 1;
while (i < m) {
dp[move][i * n + j]++;
i++;
}
i = m - 1;
while (j >= 0) {
dp[move][i * n + j]++;
j--;
}
j = 0;
while (i >= 0) {
dp[move][i * n + j]++;
i--;
}
}
int[][] dirs = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
for (int i = 2; i <= maxMove; i++) {
for (int j = 0; j < m * n; j++) {
int x = j / n;
int y = j % n;
for (int[] arr : dirs) {
int nx = x + arr[0];
int ny = y + arr[1];
if (nx >= 0 && nx < m && ny >= 0 && ny < n) {
dp[i][j] += dp[i - 1][nx * n + ny];
dp[i][j] %= 1000000007;
}
}
}
}
return dp[maxMove][startRow * n + startColumn];
}