思路

走到点(i, j),要么是从上边的点走过来的,要么是从左边的点走过来的

到达(i,j)的方式数 = 到达(i-1,j)的方式数 + 到达(i,j-1)的方式数:ways(i, j) = ways(i - 1, j) + ways(i, j - 1)ways(i,j)=ways(i−1,j)+ways(i,j−1)
可以用递归,也可以用自下而上的DP:用数组去记录子问题的解(对应递归就是子调用)
dpi:到达(i,j)的路径数(方式数)。dpi = dpi - 1 + dpidpi=dpi−1+dpi
“障碍”怎么处理
也许你会想,遇到障碍我要绕着走,但这种“动态”的想法不符合 DP “状态”的思路
我们思考单个点的“状态”:
障碍点,是无法抵达的点,是到达方式数为 0 的点,是无法从它这里走到别的点的点,即无法提供给别的点方式数的点

base case

dp0=1dp0=1 ,出发点就是终点,什么都不用做,方式数 1
第一行其余的:当前点走不了,要么是它本身是“障碍”,要么是它左边的点走不了,否则,路径数是 1,走一条直线过来
第一列其余的:当前点走不了,要么是它本身是“障碍”,要么是它上边的点走不了,否则,路径数是 1,走一条竖线过来

代码

时间复杂度 空间复杂度 都是 O(m*n)。有空会把降维的代码补充上来


const uniquePathsWithObstacles = (obstacleGrid) => {
  if (obstacleGrid[0][0] == 1) return 0; // 出发点就被障碍堵住 
  const m = obstacleGrid.length;
  const n = obstacleGrid[0].length;
  // dp数组初始化
  const dp = new Array(m);
  for (let i = 0; i < m; i++) dp[i] = new Array(n);
  // base case
  dp[0][0] = 1;                 // 终点就是出发点
  for (let i = 1; i < m; i++) { // 第一列其余的case
    dp[i][0] = obstacleGrid[i][0] == 1 || dp[i - 1][0] == 0 ? 0 : 1;
  }
  for (let i = 1; i < n; i++) { // 第一行其余的case
    dp[0][i] = obstacleGrid[0][i] == 1 || dp[0][i - 1] == 0 ? 0 : 1;
  }
  // 迭代
  for (let i = 1; i < m; i++) {
    for (let j = 1; j < n; j++) {
      dp[i][j] = obstacleGrid[i][j] == 1 ?
        0 :
        dp[i - 1][j] + dp[i][j - 1];
    }
  }
  return dp[m - 1][n - 1]; // 到达(m-1,n-1)的路径数
};

后记

现在写的题解有点啰嗦,慢慢来吧,同时也怕别人看不懂。我记得我刚看源码的时候,搜别人的文章看,自己难理解的地方没有提及,或一笔带过,小白的我只能无奈关掉。心想我自己写一定要把自己说通,也要别人容易懂,不想让人带着疑惑点进来,又带着疑惑离开。随着我理解的深入,我会精简表达并完善我的题解。

作者:xiao_ben_zhu
链接:https://leetcode-cn.com/probl...
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


小红星闪啊闪
914 声望1.9k 粉丝

时不我待