问题描述
小R正在玩一个迷宫游戏。他的起始位置位于一个n行m列的矩形棋盘的左上角 (1,1),目标是移动到棋盘的右下角 (n,m)。棋盘上的每个格子要么是道路("0"),要么是障碍("1")。小R每次只能向右或向下移动,而且必须避开障碍。请你计算小R从 (1,1) 移动到 (n,m) 的所有可能路径数,并将结果对 10^9 + 7 取模。 *测试样例 样例1:
输入:n = 2, m = 3, maze = ["000", "100"] 输出:3
样例2:
输入:n = 5, m = 5, maze = ["00000", "10000", "10000", "00000", "00000"] 输出:419
样例3:
输入:n = 3, m = 3, maze = ["010", "010", "000"] 输出:4*
思路解析
1.问题本质
*在一个 𝑛×𝑚的棋盘上,小R需要从左上角 (1,1) 移动到右下角 (n,m)。每次只能向右或向下移动,并且必须避开障碍。目标是计算所有可能的路径数。 棋盘可以看作一个带有约束条件的动态规划问题,其中需要规避障碍格子。通过合理设计状态和转移规则,可以高效地求解路径数。 本问题的本质可以概括为:
从一个网格路径统计问题出发,结合障碍物对路径的限制,引入动态规划。 动态规划的思想在于分解路径:当前格子的路径数来源于其左侧和上方格子的路径数之和。 在考虑大规模网格和复杂障碍布局时,通过动态规划递推和空间优化,使得问题能够以合理的时间和空间复杂度解决。 这个问题的核心难点在于:
路径可达性分析:需要动态判断每个格子的状态(是否是障碍)。 高效计算路径数:需要合理设计递推公式,并在复杂布局下快速计算答案。*
2.动态规划设计
2.1状态定义:
dp[i][j] 表示从起点 (1,1) 到达位置 (i,j) 的路径数。
2.2初始状态:
如果起点 (1,1) 是障碍(maze[0][0] = '1'),则路径数为 0。 否则,起点的路径数为 1,即 𝑑𝑝[1][1]=1
2.3状态转移:
如果当前位置是障碍(maze[i-1][j-1] = '1'),则 𝑑𝑝[𝑖][𝑗]=0 否则,路径数等于从上方或左侧到达当前格子的路径数之和:𝑑𝑝[𝑖][𝑗]=𝑑𝑝[𝑖−1][𝑗]+ 𝑑𝑝[𝑖][𝑗−1] 为了防止结果过大,每次计算都需要对 10的9次方+7 取模。
最终结果:
答案即为 𝑑𝑝[𝑛][𝑚],表示从 (1,1) 到 (n,m) 的所有路径数。
代码实现
def solution(n: int, m: int, maze: list) -> int:
MOD = 10**9 + 7
# 初始化 dp 数组,大小为 (n+1) x (m+1)
dp = [[0] * (m + 1) for _ in range(n + 1)]
dp[1][1] = 1 if maze[0][0] == '0' else 0
# 动态规划计算路径数
for i in range(1, n + 1):
for j in range(1, m + 1):
if maze[i-1][j-1] == '1': # 当前格子是障碍
dp[i][j] = 0
elif i == 1 and j == 1: # 起点已经初始化
continue
else:
dp[i][j] = (dp[i-1][j] + dp[i][j-1]) % MOD
return dp[n][m]
# 测试用例
if __name__ == "__main__":
assert solution(2, 3, ["000", "100"]) == 3
assert solution(5, 5, ["00000", "10000", "10000", "00000", "00000"]) == 419
assert solution(3, 3, ["010", "010", "000"]) == 4
复杂度分析
1.时间复杂度
O(n×m):需要遍历整个棋盘的每个格子,计算路径数。
2.空间复杂度
O(n×m):使用了大小为 𝑛×𝑚 的 dp 数组。 如果对空间优化(仅保留当前行和上一行的状态),则空间复杂度可降至 O(m)。
代码优化
动态规划中,计算当前行的状态仅依赖于上一行,因此可以用两个数组交替保存状态,减少空间占用。
def solution(n: int, m: int, maze: list) -> int:
MOD = 10**9 + 7
# 初始化当前行和上一行的 dp 数组
prev = [0] * (m + 1)
curr = [0] * (m + 1)
# 初始化起点
curr[1] = 1 if maze[0][0] == '0' else 0
for i in range(1, n + 1):
for j in range(1, m + 1):
if maze[i-1][j-1] == '1': # 当前格子是障碍
curr[j] = 0
elif i == 1 and j == 1: # 起点已经初始化
continue
else:
curr[j] = (prev[j] + curr[j-1]) % MOD
prev, curr = curr, [0] * (m + 1) # 更新行
return prev[m]