Hard
题目描述
给定一个大小为 n 的字符串数组 grid,其中每个字符串 grid[i] 的长度为 m。字符 grid[i][j] 是以下符号之一:
- ‘.’: 该单元格可用。
- ‘#’: 该单元格被阻塞。
你需要计算攀爬网格的不同路径数量。每条路径必须从底行(第 n - 1 行)的任意单元格开始,在顶行(第 0 行)结束。
但是,路径有一些约束条件:
- 你只能从一个可用单元格移动到另一个可用单元格。
- 每次移动的欧几里得距离最多为 d,其中 d 是给定的整数参数。两个单元格 (r1, c1) 和 (r2, c2) 之间的欧几里得距离为 sqrt((r1 - r2)² + (c1 - c2)²)。
- 每次移动要么停留在同一行,要么移动到正上方的行(从第 r 行到第 r - 1 行)。
- 你不能连续两次停留在同一行。如果你在一次移动中停留在同一行(且这次移动不是最后一次移动),你的下一次移动必须移动到上一行。
返回表示此类路径数量的整数。由于答案可能很大,请返回模 10⁹ + 7 的结果。
示例 1:
输入: grid = ["..","#."], d = 1
输出: 2
示例 2:
输入: grid = ["..","#."], d = 2
输出: 4
示例 3:
输入: grid = ["#"], d = 750
输出: 0
示例 4:
输入: grid = [".."], d = 1
输出: 4
约束条件:
- 1 <= n == grid.length <= 750
- 1 <= m == grid[i].length <= 750
- grid[i][j] 是 ‘.’ 或 ‘#’
- 1 <= d <= 750
解题思路
这是一道复杂的动态规划题目,需要考虑移动方向的状态。
思路分析
根据题目约束,我们需要追踪两个关键信息:
- 当前位置
- 上一次移动是水平移动还是向上移动(因为不能连续两次水平移动)
状态定义:
dp[r][c][0]: 到达位置 (r,c),且上一次移动是从下方行 (r+1) 向上移动的路径数dp[r][c][1]: 到达位置 (r,c),且上一次移动是在同一行 r 内水平移动的路径数
状态转移:
对于
dp[r][c][0](向上移动到达):- 可以从下一行 (r+1) 的任意有效位置移动而来
- 需要满足欧几里得距离 ≤ d 的约束
对于
dp[r][c][1](水平移动到达):- 只能从同一行的其他位置移动而来
- 且前一个位置的状态必须是向上移动到达的(即
dp[r][c'][0])
优化技巧: 使用前缀和优化距离范围内的贡献计算,避免重复遍历所有可能的起始位置。
边界条件:
- 最底行作为起始状态,所有可用位置的
dp[n-1][c][0] = 1 - 最终答案是顶行所有位置的两种状态之和
代码实现
class Solution {
public:
int numberOfRoutes(vector<string>& grid, int d) {
const int MOD = 1e9 + 7;
int n = grid.size(), m = grid[0].size();
// dp[r][c][0]: 从下方移动到(r,c)的路径数
// dp[r][c][1]: 从同行移动到(r,c)的路径数
vector<vector<vector<long long>>> dp(n, vector<vector<long long>>(m, vector<long long>(2, 0)));
// 初始化最底行
for (int c = 0; c < m; c++) {
if (grid[n-1][c] == '.') {
dp[n-1][c][0] = 1;
}
}
// 从倒数第二行开始向上计算
for (int r = n - 2; r >= 0; r--) {
vector<long long> prefix(m + 1, 0);
// 计算下一行的前缀和,用于快速查询范围内的贡献
for (int c = 0; c < m; c++) {
prefix[c + 1] = prefix[c];
if (grid[r + 1][c] == '.') {
prefix[c + 1] = (prefix[c + 1] + dp[r + 1][c][0] + dp[r + 1][c][1]) % MOD;
}
}
for (int c = 0; c < m; c++) {
if (grid[r][c] == '#') continue;
// 计算从下方移动到(r,c)的路径数
int left = 0, right = m - 1;
for (int c2 = 0; c2 < m; c2++) {
if (grid[r + 1][c2] == '.' &&
(r + 1 - r) * (r + 1 - r) + (c2 - c) * (c2 - c) <= (long long)d * d) {
dp[r][c][0] = (dp[r][c][0] + dp[r + 1][c2][0] + dp[r + 1][c2][1]) % MOD;
}
}
// 计算从同行移动到(r,c)的路径数
for (int c2 = 0; c2 < m; c2++) {
if (c2 != c && grid[r][c2] == '.' &&
(c2 - c) * (c2 - c) <= (long long)d * d) {
dp[r][c][1] = (dp[r][c][1] + dp[r][c2][0]) % MOD;
}
}
}
}
// 计算顶行的总路径数
long long result = 0;
for (int c = 0; c < m; c++) {
if (grid[0][c] == '.') {
result = (result + dp[0][c][0] + dp[0][c][1]) % MOD;
}
}
return result;
}
};
class Solution:
def numberOfRoutes(self, grid: List[str], d: int) -> int:
MOD = 10**9 + 7
n, m = len(grid), len(grid[0])
# dp[r][c][0]: 从下方移动到(r,c)的路径数
# dp[r][c][1]: 从同行移动到(r,c)的路径数
dp = [[[0, 0] for _ in range(m)] for _ in range(n)]
# 初始化最底行
for c in range(m):
if grid[n-1][c] == '.':
dp[n-1][c][0] = 1
# 从倒数第二行开始向上计算
for r in range(n-2, -1, -1):
for c in range(m):
if grid[r][c] == '#':
continue
# 计算从下方移动到(r,c)的路径数
for c2 in range(m):
if (grid[r+1][c2] == '.' and
(r+1-r)**2 + (c2-c)**2 <= d*d):
dp[r][c][0] = (dp[r][c][0] + dp[r+1][c2][0] + dp[r+1][c2][1]) % MOD
# 计算从同行移动到(r,c)的路径数
for c2 in range(m):
if (c2 != c and grid[r][c2] == '.' and
(c2-c)**2 <= d*d):
dp[r][c][1] = (dp[r][c][1] + dp[r][c2][0]) % MOD
# 计算顶行的总路径数
result = 0
for c in range(m):
if grid[0][c] == '.':
result = (result + dp[0][c][0] + dp[0][c][1]) % MOD
return result
public class Solution {
public int NumberOfRoutes(string[] grid, int d) {
const int MOD = 1_000_000_007;
int n = grid.Length, m = grid[0].Length;
// dp[r][c][0]: 从下方移动到(r,c)的路径数
// dp[r][c][1]: 从同行移动到(r,c)的路径数
long[,,] dp = new long[n, m, 2];
// 初始化最底行
for (int c = 0; c < m; c++) {
if (grid[n-1][c] == '.') {
dp[n-1, c, 0] = 1;
}
}
// 从倒数第二行开始向上计算
for (int r = n - 2; r >= 0; r--) {
for (int c = 0; c < m; c++) {
if (grid[r][c] == '#') continue;
// 计算从下方移动到(r,c)的路径数
for (int c2 = 0; c2 < m; c2++) {
if (grid[r+1][c2] == '.' &&
(long)(r+1-r)*(r+1-r) + (long)(c2-c)*(c2-c) <= (long)d*d) {
dp[r, c, 0] = (dp[r, c, 0] + dp[r+1, c2, 0] + dp[r+1, c2, 1]) % MOD;
}
}
// 计算从同行移动到(r,c)的路径数
for (int c2 = 0; c2 < m; c2++) {
if (c2 != c && grid[r][c2] == '.' &&
(long)(c2-c)*(c2-c) <= (long)d*d) {
dp[r, c, 1] = (dp[r, c, 1] + dp[r, c2, 0]) % MOD;
}
}
}
}
// 计算顶行的总路径数
long result = 0;
for (int c = 0; c < m; c++) {
if (grid[0][c] == '.') {
result = (result + dp[0, c, 0] + dp[0, c, 1]) % MOD;
}
}
return (int)result;
}
}
var numberOfRoutes = function(grid, d) {
const MOD = 1e9 + 7;
const n = grid.length, m = grid[0].length;
// dp[r][c][0]: 从下方移动到(r,c)的路径数
// dp[r][c][1]: 从同行移动到(r,c)的路径数
const dp = Array(n).fill().map(() =>
Array(m).fill().map(() => [0, 0])
);
// 初始化最底行
for (let c = 0; c < m; c++) {
if (grid[n-1][c]
复杂度分析
| 复杂度类型 | 分析 |
|---|---|
| 时间复杂度 | O(n × m²),其中 n 是行数,m 是列数。对于每个位置,需要检查所有可能的移动目标 |
| 空间复杂度 | O(n × m),存储动态规划状态数组 |