前端算法第一六零弹-不同路径 II

135 阅读1分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第16天,点击查看活动详情

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。

现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

网格中的障碍物和空位置分别用 10 来表示。

示例 1:

图片.png

输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右

示例 2:

图片.png

输入:obstacleGrid = [[0,1],[0,0]]
输出:1

提示:

  • m == obstacleGrid.length
  • n == obstacleGrid[i].length
  • 1 <= m, n <= 100
  • obstacleGrid[i][j]01

动态规划

我们用 f(i,j)f(i,j) 来表示从坐标 (0,0)(0,0) 到坐标 (i,j)(i,j) 的路径总数,u(i,j)u(i,j)表示坐标 (i,j)(i,j) 是否可行,如果坐标 (i,j)(i,j) 有障碍物,u(i,j)=0u(i, j) = 0,否则 u(i,j)=1u(i, j) = 1

因为「机器人每次只能向下或者向右移动一步」,所以从坐标 (0,0)(0,0) 到坐标 (i,j)(i,j) 的路径总数的值只取决于从坐标 (0,0)(0,0) 到坐标 (i1,j)(i−1,j) 的路径总数和从坐标 (0,0)(0,0) 到坐标 (i,j1)(i,j−1) 的路径总数,即 f(i,j)f(i,j) 只能通过 f(i1,j)f(i−1,j)f(i,j1)f(i,j−1) 转移得到。当坐标 (i,j)(i,j) 本身有障碍的时候,任何路径都到到不了 f(i,j)f(i,j),此时 f(i,j)=0f(i,j)=0;下面我们来讨论坐标 (i,j)(i,j)没有障碍的情况:如果坐标 (i1,j)(i−1,j) 没有障碍,那么就意味着从坐标 (i1,j)(i−1,j) 可以走到 (i,j)(i,j),即 (i1,j)(i−1,j) 位置对 f(i,j)f(i,j) 的贡献为 f(i1,j)f(i−1,j),同理,当坐标 (i,j1)(i,j−1)没有障碍的时候,(i,j1)(i,j−1) 位置对 f(i,j)f(i,j) 的贡献为 f(i,j1)f(i,j−1)。综上所述,我们可以得到这样的动态规划转移方程:

图片.png

/**
 * @param {number[][]} obstacleGrid
 * @return {number}
 */
var uniquePathsWithObstacles = function(obstacleGrid) {
    const [m,n] = [obstacleGrid.length,obstacleGrid[0].length]
    let dp = []
    // 构建dp数组
    for(let i =0;i<m;i++){
        dp.push([])
        for(let j=0;j<n;j++){
            if(obstacleGrid[i][j]==1){
              dp[i][j] = 0
            } else if(i==0&&j>0){
              //第一列的解
              dp[0][j] = dp[0][j-1] 
            } else if(j==0&&i>0){
              // 第一行的解
              dp[i][0] = dp[i-1][0] 
            }  else {
              // 区分dp[i][j]=0的数据
              dp[i][j] =1
            }
        }
    }
    // dp[i][j]==0 时跳出当前循环继续执行
    for(let i =1;i<m;i++){
       for(let j =1;j<n;j++){
            if(dp[i][j]==0){
             continue;
            } else {
              dp[i][j] = dp[i-1][j] + dp[i][j-1] 
            }   
       }
    }  
    return dp[m-1][n-1]

};

复杂度分析

  • 时间复杂度: O(nm)O(nm),其中 nn 为网格的行数, mm 为网格的列数。我们只需要遍历所有网格一次即可。
  • 空间复杂度: O(m)O(m)。利用滚动数组优化,我们可以只用 O(m)O(m) 大小的空间来记录当前行的 ff