63. 不同路径 II (unique paths ii)

3,875 阅读2分钟

"斐波那契,动态规划开始的地方"

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第12天,点击查看活动详情

63. 不同路径 II 题目描述:一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。现在考虑网格中有障碍物,那么从左上角到右下角将会有多少条不同的路径?网格中的障碍物和空位置分别用 1 和 0 来表示。

示例看图
输入: obstacleGrid=[[0,0,0],[0,1,0],[0,0,0]]obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出: 22
解释: 33 x 33 网格的正中间有一个障碍物。从左上角到右下角一共有 22 条不同的路径:

1. 向右 -> 向右 -> 向下 -> 向下;

2. 向下 -> 向下 -> 向右 -> 向右
image.png

中规中矩的动态规划

本题目与 # 62. 不同路径 的最优子结构相似,只是要额外处理一下"障碍物"。

1、确定 dp 状态数组

定义 dp[i][j]dp[i][j] 是走到 (i,j)(i,j) 节点的不同路径数,其中,

i[0,m),j[0,n),m=obstacleGrid.length,n=obstacleGrid[0].lengthi \in [0,m),j \in [0,n),m=obstacleGrid.length,n=obstacleGrid[0].length

2、确定 dp 状态方程

当没有"障碍物"时(obstacleGrid[i][j]==0obstacleGrid[i][j] == 0),dp[i][j]=dp[i1][j]+dp[i][j1]dp[i][j] = dp[i - 1][j] + dp[i][j - 1];

当存在"障碍物"时(obstacleGrid[i][j]==1obstacleGrid[i][j] == 1),dp[i][j]=0dp[i][j] = 0,因为没有一条路能到达这里。

3、确定 dp 初始状态

i=0i=0 时,对于 第一个 j1[0,n)j_1 \in [0,n), 且 obstacleGrid[0][j1]==1obstacleGrid[0][j_1] == 1,那么

  • j[j1,n)j \in [j_1,n),都有 dp[0][j]=0dp[0][j] = 0

  • j[0,j1)j \in [0,j_1),都有 dp[0][j]=1dp[0][j] = 1

j=0j=0 时,对于 第一个 i1[0,m)i_1 \in [0,m), 且 obstacleGrid[i1][0]==1obstacleGrid[i_1][0] == 1,那么

  • i[i1,m)i \in [i_1,m),都有 dp[i][0]=0dp[i][0] = 0

  • i[0,i1)i \in [0,i_1),都有 dp[i][0]=1dp[i][0] = 1

4、确定遍历顺序

  • 第一层循环从 i=1i=1i=m1i=m-1;

  • 第二层循环从 j=1j=1j=n1j=n-1

NOTE: 先遍历 ii 后遍历 jj,或者先遍历 jj 后遍历 ii 均可。

5、确定最终返回值

回归到状态定义中,dp[m1][n1]dp[m-1][n-1] 是走到 (m,n)(m,n) 节点的不同路径数。

6、代码示例

/**
 * 空间复杂度 O(m*n)
 * 时间复杂度 O(m*n)
 */
function uniquePathsWithObstacles(obstacleGrid: number[][]): number {
    const m = obstacleGrid.length;
    const n = obstacleGrid[0].length;
    const dp = Array.from({ length: m }, () => new Array(n).fill(0));
    
    for(let i = 0; i < m; i++) {
        if(obstacleGrid[i][0]) break;
        dp[i][0] = 1;
    }
    
    for(let j = 0; j < n; j++) {
        if(obstacleGrid[0][j]) break;
        dp[0][j] = 1;
    }

    for(let i = 1; i < m; i++) {
        for(let j = 1; j < n; j++) {
            if (obstacleGrid[i][j]) continue;
      
            dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
        }
    }

    return dp[m - 1][n - 1];
}

调整遍历顺序

/**
 * 空间复杂度 O(m*n)
 * 时间复杂度 O(m*n)
 */
function uniquePathsWithObstacles(obstacleGrid: number[][]): number {
    const m = obstacleGrid.length;
    const n = obstacleGrid[0].length;
    const dp = Array.from({ length: m }, () => new Array(n).fill(0));
    
    for(let i = 0; i < m; i++) {
        if(obstacleGrid[i][0]) break;
        dp[i][0] = 1;
    }
    
    for(let j = 0; j < n; j++) {
        if(obstacleGrid[0][j]) break;
        dp[0][j] = 1;
    }

    for(let j = 1; j < n; j++) {
        for(let i = 1; i < m; i++) {
            if (obstacleGrid[i][j]) continue;
      
            dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
        }
    }

    return dp[m - 1][n - 1];
}

参考

# 重识动态规划