动态规划 & 出界的路径数

298 阅读2分钟

前言

“这是我参与8月更文挑战的第27天,活动详情查看:[8月更文挑战]

  1. 576. 出界的路径数

出界的路径数

给你一个大小为 m x n 的网格和一个球。球的起始坐标为 [startRow, startColumn] 。你可以将球移到在四个方向上相邻的单元格内(可以穿过网格边界到达网格之外)。你 最多 可以移动 maxMove 次球。

给你五个整数 mnmaxMovestartRow 以及 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 <= 50
  • 0 <= maxMove <= 50
  • 0 <= startRow < m
  • 0 <= 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];
}