LeetCode算法系列 63. 不同路径 II

288 阅读2分钟

白菜Java自习室 涵盖核心知识

LeetCode算法系列(Java版) 62. 不同路径
LeetCode算法系列(Java版) 63. 不同路径 II

力扣原题

63. 不同路径 II

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

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

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

robot_maze.png

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

示例 1

robot1.jpg

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

示例 2

robot2.jpg

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

解题思路

根据上一篇 LeetCode算法系列 62. 不同路径,我们知道使用动态规划来解决这个问题。

我们用 f(i,j)f(i, j) 表示从左上角走到 (i,j)(i, j) 的路径数量,其中 iijj 的范围分别是 [0,m)[0, m)[0,n)[0, n)

由于我们每一步只能从向下或者向右移动一步,因此要想走到 (i,j)(i, j),如果向下走一步,那么会从 (i1,j)(i-1, j) 走过来;如果向右走一步,那么会从 (i,j1)(i, j-1) 走过来。因此我们可以写出动态规划转移方程:

f(i,j)=f(i1,j)+f(i,j1)f(i,j)=f(i−1,j)+f(i,j−1)

那我们现在引入障碍物,该怎么办呢?

我们用 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)=1u(i, j) = 1,否则 u(i,j)=0u(i, j) = 0假如存在障碍物,u(i,j)=1u(i, j) = 1,相当于我这条路径废了,f(i,j)f(i,j) 并不能从 f(i1,j)f(i−1,j)f(i,j1)f(i,j−1) 到达,而 f(i,j)=0f(i,j) = 0。综上所述,我们可以得到这样的动态规划转移方程:

f(i,j)={0,u(i,j)=1f(i1,j)+f(i,j1),u(i,j)=0f(i, j) = \left \{ \begin{aligned} 0 & , & u(i, j) = 1 \\ f(i - 1, j) + f(i, j - 1) & , & u(i, j) = 0 \end{aligned} \right.

代码实现

class Solution {
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
    
        // 定义 dp 数组并初始化第 1 行和第 1 列。
        int m = obstacleGrid.length, n = obstacleGrid[0].length;
        int[][] dp = new int[m][n];
        for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) {
            dp[i][0] = 1;
        }
        for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) {
            dp[0][j] = 1;
        }

        // 根据状态转移方程 dp[i][j] = dp[i - 1][j] + dp[i][j - 1] 进行递推。
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (obstacleGrid[i][j] == 0) {
                    dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
                }
            }
        }
        return dp[m - 1][n - 1];
    }
}

复杂度分析

  • 时间复杂度O(mn)O(mn)

  • 空间复杂度O(mn)O(mn),即为存储所有状态需要的空间。注意到 f(i,j)f(i, j) 仅与第 ii 行和第 i1i-1 行的状态有关,因此我们可以使用滚动数组代替代码中的二维数组,使空间复杂度降低为 O(n)O(n)。此外,由于我们交换行列的值并不会对答案产生影响,因此我们总可以通过交换 mmnn 使得 mnm \leq n,这样空间复杂度降低至 O(min(m,n))O(\min(m, n))

LeetCode算法系列(Java版) 62. 不同路径
LeetCode算法系列(Java版) 63. 不同路径 II