LeetCode 63 Unique Paths II (Tag:Array Difficulty:Medium)

209 阅读2分钟

这是我参与8月更文挑战的第17天,活动详情查看:8月更文挑战

前言

关于 LeetCode 数组类型题目的相关解法,可见LeetCode 数组类型题目做前必看,分类别解法总结了题目,可以用来单项提高。觉得有帮助的话,记得多多点赞关注哦,感谢!

题目描述

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

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

现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
image.png
网格中的障碍物和空位置分别用 1 和 0 来表示。

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

  1. 向右 -> 向右 -> 向下 -> 向下
  2. 向下 -> 向下 -> 向右 -> 向右

示例 2:
image.png
输入:obstacleGrid = [[0,1],[0,0]]
输出:1

链接:leetcode-cn.com/problems/un…

题解

这道题目采用记忆化递归的方法来做.首先解释一下什么是记忆化递归.

拿经典的递归题目, 斐波那契数列来讲, 斐波那契数列以如下被以递推的方法定义:F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2), 那么如何求 F(n) 的大小呢? 我们通常的解法就是递归

const f = (n) => {
    if (n <= 1) return n
    return f(n-1) + f(n-2)
}

这种递归存在的问题是同一个 f(n) 有可能被重复计算, 比如计算 f(10), 那么根据递归公式, f(10) = f(9) + f(8), 而 f(9) = f(8) + f(7), 可以看到在计算 f(10) 和 f(9) 的时候都需要计算 f(8) 的值.

记忆化递归就是用一个数组记录下已经计算好的 f(n) 值, 当下一次用到时, 直接从数组中拿出来, 而不用再次递归的调用, 也算是空间换时间的一种做法. 斐波那契数列问题用记忆化递归的方法来做就是

const m = new Array(n).fill(0)
cosnt f = (n) => {
    if (n <= 1) return n
    if (!m[n]) {
        m[n] = f(n - 1) + f(n - 2) 
    }
    return m[n]
}

上面的解法中, 我们用数组 m 来记录了 f(n) 是否有被计算过, 如果计算过了就直接返回 m[n], 否则就递归计算 m[n] = f(n - 1) + f(n -2)

这种记忆化递归的方法使得算法的时间复杂度变为 O(n), 相应的空间复杂度增加为了 O(n)

那么, 对于本题来讲, 我们也采用记忆化递归的方式来做. 有以下几个要点:

  1. 新建一个 f 数组, 作为记忆化递归的记录数组, 数组初始化为 -1, 用来判断该位置是否计算过递归的值

  2. 边界条件是当递归出界时返回 0, 当递归到出发点时返回 1

  3. 如果遇到了障碍物, 则记忆化递归的数组对应位置值为 0, 否则递归计算

具体的代码见下方, 要注意的点是记忆数组范围是 m + 1 和 n + 1, 所以对应的 obstacleGrid 数组是 i - 1 和 j - 1, 而不是 i, j.

时间复杂度为 O(mn), 空间复杂度为 O(mn)

/**
 * @param {number[][]} obstacleGrid
 * @return {number}
 */
var uniquePathsWithObstacles = function(obstacleGrid) {
    let m = obstacleGrid.length
    let n = obstacleGrid[0].length
    let f = Array.from({ length: m + 1 }, () => (new Array(n + 1).fill(-1)))
    if (obstacleGrid[0][0] === 1) return 0

    const paths = (i, j) => {
      if (i <= 0 || j <= 0) return 0
      if (i === 1 && j === 1) return 1
      if (f[i][j] !== -1 ) return f[i][j]
      if (obstacleGrid[i-1][j-1] === 1) {
        f[i][j] = 0
      } else {
        f[i][j] = paths(i-1, j) + paths(i, j-1)
      }
      return f[i][j]
    }
  
    return paths(m, n)
};