leetcode
leetcode 501 ~ 550
出界的路径数

出界的路径数

难度:

标签:

题目描述

给你一个大小为 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

代码结果

运行时间: 65 ms, 内存: 21.4 MB


/*
 * 思路:
 * 同样的动态规划思路,这里用 Java Stream API 简化代码。
 * 我们通过流处理来更新 dp 数组,保留和传统实现相同的逻辑。
 */
import java.util.stream.IntStream;
 
public class Solution {
    private static final int MOD = 1000000007;
 
    public int findPaths(int m, int n, int maxMove, int startRow, int startColumn) {
        int[][][] dp = new int[m][n][maxMove + 1];
        dp[startRow][startColumn][0] = 1;
        int count = 0;
 
        for (int move = 1; move <= maxMove; move++) {
            int[][][] temp = new int[m][n][maxMove + 1];
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    int x = i;
                    int y = j;
                    IntStream.of(-1, 1).forEach(delta -> {
                        if (x + delta < 0 || x + delta >= m || y < 0 || y >= n) {
                            count = (count + dp[x][y][move - 1]) % MOD;
                        } else if (y + delta < 0 || y + delta >= n || x < 0 || x >= m) {
                            count = (count + dp[x][y][move - 1]) % MOD;
                        } else {
                            temp[x + delta][y][move] = (temp[x + delta][y][move] + dp[x][y][move - 1]) % MOD;
                            temp[x][y + delta][move] = (temp[x][y + delta][move] + dp[x][y][move - 1]) % MOD;
                        }
                    });
                }
            }
            dp = temp;
        }
 
        return count;
    }
}
 

解释

方法:

这个题解使用了记忆化递归的方法。首先判断如果最大移动次数为0,直接返回0。然后定义了一个递归函数countPaths,用来计算从当前位置(currX, currY)出发,在剩余的maxMove步内出界的路径数量。如果只剩下1步,那么只需判断当前位置是否在边界上,在的话就返回1,否则返回0。如果还有多步,就分别向上下左右四个方向递归调用countPaths,如果某个方向已经出界,则该方向的返回值为1。最后将四个方向的结果求和取模即可。主函数中调用countPaths(startRow, startColumn, maxMove)得到最终结果。

时间复杂度:

O(m * n * maxMove)

空间复杂度:

O(m * n * maxMove)

代码细节讲解

🦆
为什么在递归函数`countPaths`中,当只剩下1步时,是通过检查当前位置是否在边界上来决定是否返回1,这样的逻辑是否确保了所有可能的出界路径都被正确计算?
在递归函数`countPaths`中,当只剩下1步时,通过检查当前位置是否在边界上来确定是否返回1,确实确保了所有可能的出界路径被正确计算。这里的逻辑基于的是,如果还有一步可以走,且当前位置已经在边界上(顶部、底部、左侧或右侧),那么在下一步必然可以直接走出界,因此直接返回1代表这一可能性。如果不在边界上,则无法在一步内走出界,因此返回0。这样的处理方式精确地覆盖了所有能在最后一步直接走出界的情况。
🦆
题解中提到,如果某个方向已经出界,则该方向的返回值为1。请问这种处理方式是否可能导致对某些路径的重复计数,特别是在接近边界的位置?
这种处理方式不会导致路径的重复计数。在题解中,每个递归调用都是基于当前位置和剩余可移动的步数来决定的。如果某个方向已经出界,即当前方向的下一步没有有效的格子可走,直接返回1是因为这确实代表了一条成功的出界路径。由于每次递归调用都是独立考虑各个方向的,所以即使在边界附近,每个方向的出界都是独立计算,不会重复。
🦆
在没有任何移动时(maxMove为0),直接返回0的做法是否考虑了所有边界条件,例如如果开始位置就在边缘上是否应该有不同的返回值?
在没有任何移动时直接返回0是合理的,因为题目要求的是在给定的移动次数内出边界的路径数量。即使开始位置已经在边界上,如果没有移动次数(maxMove为0),则没有任何移动可以执行,因此不能形成任何有效的出界路径。这种设定是基于题目要求的移动次数内完成出界的规则。
🦆
题解说明了使用`@cache`来避免重复计算,但如何确保缓存机制在这种多参数递归函数中有效提高性能?
使用`@cache`装饰器可以有效地避免在递归过程中对相同参数值的重复计算,从而显著提高性能。在这个问题中,递归函数`countPaths`有三个参数:当前坐标(currX, currY)和剩余可移动步数(maxMove)。由于在递归过程中可能会多次访问到相同的位置与相同的剩余步数,使用缓存可以保存这些结果,避免重复的计算过程。每次调用时,如果这些参数的组合之前已经计算过,就直接从缓存中返回结果,减少了计算量和执行时间。

相关问题

骑士在棋盘上的概率

在一个 n x n 的国际象棋棋盘上,一个骑士从单元格 (row, column) 开始,并尝试进行 k 次移动。行和列是 从 0 开始 的,所以左上单元格是 (0,0) ,右下单元格是 (n - 1, n - 1)

象棋骑士有8种可能的走法,如下图所示。每次移动在基本方向上是两个单元格,然后在正交方向上是一个单元格。

每次骑士要移动时,它都会随机从8种可能的移动中选择一种(即使棋子会离开棋盘),然后移动到那里。

骑士继续移动,直到它走了 k 步或离开了棋盘。

返回 骑士在棋盘停止移动后仍留在棋盘上的概率

 

示例 1:

输入: n = 3, k = 2, row = 0, column = 0
输出: 0.0625
解释: 有两步(到(1,2),(2,1))可以让骑士留在棋盘上。
在每一个位置上,也有两种移动可以让骑士留在棋盘上。
骑士留在棋盘上的总概率是0.0625。

示例 2:

输入: n = 1, k = 0, row = 0, column = 0
输出: 1.00000

 

提示:

  • 1 <= n <= 25
  • 0 <= k <= 100
  • 0 <= row, column <= n - 1