题目描述
网格里 1 是障碍、0 可走,每步只能向右或向下,求从左上角到右下角的不同路径数。
思路解析
最直接的想法是从起点递归,每格往右、往下各试一次,数到终点的路径条数。可同一个格子会被无数条前缀重复访问,「从某格到终点有几条路」被反复重算,网格一大就指数爆炸。
转折:用 dp[i][j] 记「走到 (i,j) 有几条路」,每格只算一次。一个格子只能从上面或左边来,所以 dp[i][j] = dp[i−1][j] + dp[i][j−1]。遇到障碍,谁也站不上去,dp 直接置 0——它就不会再把路径数传给右边和下边,那些经过障碍的路自动被掐断。
建表 · 起点 dp[0][0]=1:建一张 3×3 的 dp 表(行列数全程不变)。起点 (0,0) 不是障碍,站在那儿本身就算 1 条路,dp[0][0]=1。其余先空着,按「先上后下、先左后右」的顺序逐格填。
第一行 · 只能向右:第一行的格子头顶没有格子,只能一路向右走过来,且这一行没障碍,所以全是 1(一条直路)。
第一列 · 只能向下:第一列同理,只能一路向下,没障碍,也全是 1。现在边界铺好了,开始填中间。
障碍格 (1,1) = 0:轮到正中央 (1,1),它是障碍。这里就是负例分支:不走「上+左」那套,直接把 dp 记成 0——没有任何走法能合法停在障碍上。这个 0 待会儿会传给它右边和下边。
填 (1,2) · 上1 + 左0:(1,2) 不是障碍,正常算「上+左」:上面 dp[0][2]=1,左边是障碍传来的 0。所以只剩 1 条路。障碍的 0 在这里第一次起作用,把原本能从左边来的路抹掉了。
填 (2,1) · 上0 + 左1:(2,1) 同样算「上+左」:上面又是障碍传来的 0,左边 dp[2][0]=1。结果 1 条路。可以看到障碍的 0 同时切断了右边和下边两个方向。
右下角 · 上1 + 左1:终点 (2,2) = 上面 dp[1][2]=1 + 左边 dp[2][1]=1 = 2。这正是绕过中央障碍的那 2 条路:「上面绕」和「下面绕」各一条。
答案:右下角 dp[2][2] = 2 就是答案。整张表填完,障碍那格的 0 把所有穿过它的路都拦住了。
网格 DP 里碰到「不可用的格子」,把它的 dp 置 0(计数题)或置无穷(最值题),它就不会再把值传给右边、下边——障碍、陷阱、禁区都这么处理。
参考代码(Python)
1def uniquePathsWithObstacles(self, grid):
2 if grid[0][0] == 1: return 0 # 起点就是障碍,无路
3 m, n = len(grid), len(grid[0])
4 dp = [[0] * n for _ in range(m)]
5 dp[0][0] = 1
6 for i in range(m):
7 for j in range(n):
8 if grid[i][j] == 1: # 障碍格:清零并跳过
9 dp[i][j] = 0; continue
10 if i: dp[i][j] += dp[i-1][j] # 上
11 if j: dp[i][j] += dp[i][j-1] # 左
12 return dp[m-1][n-1]复杂度分析
- 时间复杂度:O(m × n) —— 每个格子只算一次「上+左」
- 空间复杂度:O(m × n) —— 一张 m×n 的 dp 表,可压成一行 O(n)
套路模板
记住骨架:障碍格清零跳过、其余「上+左」累加。把「相加」换成 min/max,就是带障碍的最小/最大路径和。
1dp = [[0]*n for _ in range(m)]
2dp[0][0] = 1 # 起点为障碍要先特判返回 0
3for i in range(m):
4 for j in range(n):
5 if 不可用(i, j): # 障碍清零跳过
6 dp[i][j] = 0; continue
7 if i: dp[i][j] += dp[i-1][j] # 上
8 if j: dp[i][j] += dp[i][j-1] # 左
9return dp[m-1][n-1]易错点
- 错误写法:dp[0][0]=1 后直接进循环,不判起点是不是障碍 → 正确写法:if grid[0][0]==1: return 0 先特判(起点就是障碍时整条路都不存在,可此时 dp[0][0] 被你写成了 1,答案直接错)
- 错误写法:障碍格也照算 dp[i][j] += 上 + 左 → 正确写法:障碍格 dp[i][j]=0 然后 continue(不跳过的话,障碍格会把上+左的路径数累进来再传给后面,凭空多出穿过障碍的路)
- 错误写法:i=0 或 j=0 时直接访问 dp[i-1][j] / dp[i][j-1] → 正确写法:用 if i / if j 守卫,第一行只加左、第一列只加上(第一行没有「上」、第一列没有「左」,硬取会读到错误的边界格)
以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握。