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

网格中的障碍物和空位置分别用 1 和 0 来表示。
说明:m 和 n 的值均不超过 100。
示例 1:
输入: [ [0,0,0], [0,1,0], [0,0,0] ] 输出: 2 解释: 3x3 网格的正中间有一个障碍物。 从左上角到右下角一共有 2 条不同的路径:
- 向右 -> 向右 -> 向下 -> 向下
- 向下 -> 向下 -> 向右 -> 向右
解析:
看到这个题目,我们很容易就想到动态规划。然后我们开始分析:
如果我们用 f(i, j)来表示从坐标 (0, 0)到坐标 (i, j)的路径总数,很容易我们就能得出以下结论:
- 如果坐标(i,j)处有障碍物,则f(i,j) = 0。
- 因为机器人智能向下、向右,要想到达坐标(i,j)处,只能从坐标(i-1,j)处或坐标(i,j-1)处到达,即f(i,j)=f(i-1,j)+f(i,j-1)。
由此我们有了第一种解题算法:
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length;
if (m == 0) {
return 0;
}
int n = obstacleGrid[0].length;
if (n == 0) {
return 0;
}
if (obstacleGrid[0][0] == 1 || obstacleGrid[m - 1][n - 1] == 1) {
return 0;
}
int[][] f = new int[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (obstacleGrid[i][j] == 1) {
f[i][j] = 0;
continue;
}
if(i == 0 && j == 0){
f[i][j] = 1;
}
f[i][j] = (i == 0 ? 0 : f[i - 1][j]) + (j == 0 ? 0 : f[i][j - 1]);
}
}
return f[m - 1][n - 1];
}
经运行,是可以得出正确结果的。
复杂度分析
- 时间复杂度:O(mn),其中 m 为网格的行数,n 为网格的列数。我们只需要遍历所有网格一次即可。
- 空间复杂度:O(mn)。每个网格都有一个路径。
我们再来思考下是否有优化的方法?
我们看到代码中我们用二阶数组来表示各个点的路径总数,其实f(i,j)只与f(i-1,j)和f(i,j-1)有关。如果计算下一行的路径总数时直接保存到上一行,就可以简化成f(j) = f'(j)+ f(j-1),其中f'(j)表示上一行坐标j的路径总数,这样可以优化储存的空间,这就是滚动数组。优化后的代码如下:
public int uniquePathsWithObstacles1(int[][] obstacleGrid) {
int m = obstacleGrid.length;
if (m == 0) {
return 0;
}
int n = obstacleGrid[0].length;
if (n == 0) {
return 0;
}
if (obstacleGrid[0][0] == 1 || obstacleGrid[m - 1][n - 1] == 1) {
return 0;
}
int[] f = new int[n];
f[0] = 1;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (obstacleGrid[i][j] == 1) {
f[j] = 0;
continue;
}
if (j - 1 >= 0) {
f[j] += f[j - 1];
}
}
}
return f[n - 1];
}
复杂度分析
- 时间复杂度:O(mn),其中 m 为网格的行数,n 为网格的列数。我们只需要遍历所有网格一次即可。
- 空间复杂度:O(n)。利用滚动数组优化,我们可以只用 O(n) 大小的空间来记录当前行的 f 值。