题目描述
思路解析
一句话答案:LeetCode 63 不同路径 II 在带障碍网格里数路径,二维 DP:dp[i][j]=dp[i-1][j]+dp[i][j-1] 记走到该格的路数,障碍格置 0 阻断下游,时间 O(m×n)、空间可压 O(n)。
不同路径 II 到底在数什么
给一个 m×n 网格,0 是空地、1 是障碍。机器人从左上角 (0,0) 出发,每步只能向右或向下,问走到右下角、不踩任何障碍的不同路径有多少条。本题网格是 4 行 6 列,障碍摆在 (0,3)、(1,1)、(2,3),答案是 7。
为什么不能把每条路都走一遍数
最直接是把每条路都走出来数一遍:从左上到右下走 m+n-2 步、每步二选一,路径数随网格指数级膨胀,稍大就数不完。浪费在于:走到中间某格的路数,会在很多条完整路径里被反复重算。既然「走到某格有几条路」这个子问题能独立回答,就算一次存下来,指数枚举压成一格一格填的递推——用填好的格子推当前格,就是动态规划(DP,把子问题的答案记下来别重算)。
dp[i][j] 记什么,障碍格为什么直接记 0
定义 dp[i][j] 为「从起点走到格子 (i,j) 的不同路径数」。走到 (i,j) 的最后一步,要么从正上方 (i-1,j) 下来、要么从正左方 (i,j-1) 过来,两批路互不重叠,所以 dp[i][j]=dp[i-1][j]+dp[i][j-1]。起点 dp[0][0]=1,原地不动也算一种走法。
和无障碍版唯一的差别就在障碍格:机器人踏不进去,走到它的路数只能是 0,遇到障碍直接 dp[i][j]=0,不做相加。
障碍格的 0 怎么自然把下游路径掐断
障碍格记 0,会顺着转移式(由已算好的格子推出当前格子的那条公式)自动把下游的路堵死,不用额外判断。障碍被置 0 后,它下方和右方的格子相加时,把这个 0 当成一条来路,相当于「从障碍过来有 0 条路」。比如障碍右邻那格,正常是「上方 + 左方」,可左方是障碍的 0,只剩上方一条贡献。障碍一格 0,只掐掉穿过它的路,不会误伤其余。
拿题面 4×6 网格把整张表填一遍
按题面 4×6 网格逐格填。第 0 行:dp[0][0]=1,(0,1)(0,2) 只有左边一条来路照搬得 1、1;(0,3) 障碍记 0;(0,4)(0,5) 唯一来路是左边那个 0,也跟着变 0。
第 1 行:(1,0) 从上方来是 1;(1,1) 障碍记 0;(1,2)=1+0=1,(1,3)=0+1=1,(1,4)=0+1=1,(1,5)=0+1=1。第 2 行:(2,0)=1,(2,1)=0+1=1,(2,2)=1+1=2,(2,3) 障碍记 0,(2,4)=1+0=1,(2,5)=1+1=2。
第 3 行:(3,0)=1,(3,1)=1+1=2,(3,2)=2+2=4,(3,3)=0+4=4,(3,4)=1+4=5,(3,5)=2+5=7。右下角 dp[3][5]=7,就是绕开 3 个障碍的路径总数。
复杂度多少,起点和首行遇障碍两个边界
每格只算一次加法、共 m×n 格,时间 O(m×n);dp 表可压成一行滚动更新(滚动数组:只留一行滚动覆盖),空间从 O(m×n) 降到 O(n)。
两个边界最容易踩坑。一是起点就是障碍:出发点都踏不进去,直接返回 0,别再往下填。二是首行或首列遇障碍:这一行/列只有一条直线来路,障碍之后的格子来路被掐断,必须跟着全变 0,无脑填 1 就写错了。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
和无障碍版唯一的差别:遇到障碍格,这一格直接填 0,不再相加。记住这一点,下面每格都在套它。
起点 (0,0):原地不动也算一种走法,dp[0][0] = 1。
(0,1) 在边上,只有一条来路(左方 dp[0][0]=1),照搬过来 dp[0][1] = 1。
(0,2) 在边上,只有一条来路(左方 dp[0][1]=1),照搬过来 dp[0][2] = 1。
(0,3) 是障碍格(█),机器人根本踏不进来,路径数记 0——它不给下游贡献任何走法。
(0,4) 在边上,唯一来路(左方 dp[0][3]=0)已经是 0——前面被障碍掐断了,所以这里也走不到,dp=0。
(0,5) 在边上,唯一来路(左方 dp[0][4]=0)已经是 0——前面被障碍掐断了,所以这里也走不到,dp=0。
(1,0) 在边上,只有一条来路(上方 dp[0][0]=1),照搬过来 dp[1][0] = 1。
(1,1) 是障碍格(█),机器人根本踏不进来,路径数记 0——它不给下游贡献任何走法。
算 (1,2):正上方 dp[0][2]=1(蓝),正左方 dp[1][1]=0(蓝),两条来路汇到这里。
相加:1 + 0 = 1,填进 (1,2)。有一条来路是 0(被障碍堵了),只剩另一条贡献。
算 (1,3):正上方 dp[0][3]=0(蓝),正左方 dp[1][2]=1(蓝),两条来路汇到这里。
相加:0 + 1 = 1,填进 (1,3)。有一条来路是 0(被障碍堵了),只剩另一条贡献。
算 (1,4):正上方 dp[0][4]=0(蓝),正左方 dp[1][3]=1(蓝),两条来路汇到这里。
相加:0 + 1 = 1,填进 (1,4)。有一条来路是 0(被障碍堵了),只剩另一条贡献。
算 (1,5):正上方 dp[0][5]=0(蓝),正左方 dp[1][4]=1(蓝),两条来路汇到这里。
相加:0 + 1 = 1,填进 (1,5)。有一条来路是 0(被障碍堵了),只剩另一条贡献。
(2,0) 在边上,只有一条来路(上方 dp[1][0]=1),照搬过来 dp[2][0] = 1。
算 (2,1):正上方 dp[1][1]=0(蓝),正左方 dp[2][0]=1(蓝),两条来路汇到这里。
相加:0 + 1 = 1,填进 (2,1)。有一条来路是 0(被障碍堵了),只剩另一条贡献。
算 (2,2):正上方 dp[1][2]=1(蓝),正左方 dp[2][1]=1(蓝),两条来路汇到这里。
相加:1 + 1 = 2,填进 (2,2)。从上面来的、从左边来的,合起来就这么多。
(2,3) 是障碍格(█),机器人根本踏不进来,路径数记 0——它不给下游贡献任何走法。
算 (2,4):正上方 dp[1][4]=1(蓝),正左方 dp[2][3]=0(蓝),两条来路汇到这里。
相加:1 + 0 = 1,填进 (2,4)。有一条来路是 0(被障碍堵了),只剩另一条贡献。
算 (2,5):正上方 dp[1][5]=1(蓝),正左方 dp[2][4]=1(蓝),两条来路汇到这里。
相加:1 + 1 = 2,填进 (2,5)。从上面来的、从左边来的,合起来就这么多。
(3,0) 在边上,只有一条来路(上方 dp[2][0]=1),照搬过来 dp[3][0] = 1。
算 (3,1):正上方 dp[2][1]=1(蓝),正左方 dp[3][0]=1(蓝),两条来路汇到这里。
相加:1 + 1 = 2,填进 (3,1)。从上面来的、从左边来的,合起来就这么多。
算 (3,2):正上方 dp[2][2]=2(蓝),正左方 dp[3][1]=2(蓝),两条来路汇到这里。
相加:2 + 2 = 4,填进 (3,2)。从上面来的、从左边来的,合起来就这么多。
算 (3,3):正上方 dp[2][3]=0(蓝),正左方 dp[3][2]=4(蓝),两条来路汇到这里。
相加:0 + 4 = 4,填进 (3,3)。有一条来路是 0(被障碍堵了),只剩另一条贡献。
算 (3,4):正上方 dp[2][4]=1(蓝),正左方 dp[3][3]=4(蓝),两条来路汇到这里。
相加:1 + 4 = 5,填进 (3,4)。从上面来的、从左边来的,合起来就这么多。
算 (3,5):正上方 dp[2][5]=2(蓝),正左方 dp[3][4]=5(蓝),两条来路汇到这里。
相加:2 + 5 = 7,填进 (3,5)。从上面来的、从左边来的,合起来就这么多。
表填满了。右下角 dp[3][5] = 7,就是绕开 3 个障碍、从左上走到右下的不同路径总数。
极端输入先想清楚:起点/终点是障碍都要能正确返回 0。
两个高频追问。
参考代码
def uniquePathsWithObstacles(g): m, n = len(g), len(g[0]) if g[0][0] == 1: return 0 dp = [[0]*n for _ in range(m)] dp[0][0] = 1 for i in range(m): for j in range(n): if g[i][j] == 1: dp[i][j] = 0 elif i or j: up = dp[i-1][j] if i else 0 lf = dp[i][j-1] if j else 0 dp[i][j] = up + lf return dp[m-1][n-1]复杂度
- 时间:O(m×n),每格只算一次
- 空间:O(m×n),存整张表(可压成一行 O(n))
易错点
面试追问把动画讲成自己的话
追问能优化到 O(n) 空间吗?
追问终点是障碍会怎样?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。再去主线里挑下一道练手。
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题