Leetcode 63:不同路径 II(最详细的解法!!!)

版权声明:本文为博主原创文章,未经博主允许不得转载。有事联系:[email protected] https://blog.csdn.net/qq_17550379/article/details/82826800

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

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

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

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

说明: mn 的值均不超过 100。

示例 1:

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

解题思路

这个问题是之前Leetcode 62:不同路径(最详细的解法!!!) 的一个衍生。仅仅在原来问题的基础之上添加一个判断就可以解决。但是还有一个细节的地方需要注意,我们原先的边界条件是

if row == m - 1 or col == n - 1: return 1

但是这个问题不同,例如这种情况

0 1
1 0

显然,对于(0,1)(1,0)这两个点来说,我们没有路径到达终点。所以,对于这个问题的边界条件要变成

if row == m - 1 and col == n - 1: return 1

并且,我们要在一开始判断obstacleGrid[-1][-1]是否为0。最终代码为

class Solution:
    def uniquePathsWithObstacles(self, obstacleGrid):
        """
        :type obstacleGrid: List[List[int]]
        :rtype: int
        """        
        if not obstacleGrid or obstacleGrid[-1][-1]:
            return 0

        m, n = len(obstacleGrid), len(obstacleGrid[0])
        return self._obstacleGrid(obstacleGrid, m, n, 0, 0)

    def _obstacleGrid(self, grid, m, n, row, col):
        if row == m - 1 and col == n - 1:
            return 1

        if row >= m or col >= n or grid[row][col] == 1:
            return 0

        return self._obstacleGrid(grid, m, n, row + 1, col) + self._obstacleGrid(grid, m, n, row, col + 1)   

但是这样做存在着大量的重复运算(在哪呢?)。我们可以通过记忆化搜索的方式来优化上面的问题。我们通常的写法如下

class Solution:
    def uniquePathsWithObstacles(self, obstacleGrid):
        """
        :type obstacleGrid: List[List[int]]
        :rtype: int
        """        
        if not obstacleGrid or obstacleGrid[-1][-1]:
            return 0

        m, n = len(obstacleGrid), len(obstacleGrid[0])
        mem = [[None]*n for _ in range(m)]

        return self._obstacleGrid(obstacleGrid, m, n, 0, 0, mem)

    def _obstacleGrid(self, grid, m, n, row, col, mem):
        if row == m - 1 and col == n - 1:
            return 1

        if row >= m or col >= n or grid[row][col] == 1:
            return 0
        
        if row < m and col < n and mem[row][col] != None:
            return mem[row][col]

        mem[row][col] = self._obstacleGrid(grid, m, n, row + 1, col, mem) + \
                            self._obstacleGrid(grid, m, n, row, col + 1, mem)      
        return mem[row][col]

这里的实现上我们要注意一个细节

if row < m and col < n and mem[row][col] != None:

而不是

if row < m and col < n and mem[row][col]:

为什么呢?因为我们的mem中存储了0。另外我们除了通过list实现mem外,还可以通过dict实现。

class Solution:
    def uniquePathsWithObstacles(self, obstacleGrid):
        """
        :type obstacleGrid: List[List[int]]
        :rtype: int
        """        
        if not obstacleGrid or obstacleGrid[-1][-1]:
            return 0

        m, n = len(obstacleGrid), len(obstacleGrid[0])
        mem = dict()

        return self._obstacleGrid(obstacleGrid, m, n, 0, 0, mem)

    def _obstacleGrid(self, grid, m, n, row, col, mem):
        tmp = '{}, {}'.format(row, col)
        if row == m - 1 and col == n - 1:
            return 1

        if row >= m or col >= n or grid[row][col] == 1:
            return 0
        
        if tmp in mem:
            return mem[tmp]

        mem[tmp] = self._obstacleGrid(grid, m, n, row + 1, col, mem) + \
                            self._obstacleGrid(grid, m, n, row, col + 1, mem)      
        return mem[tmp]

我们同样可以通过迭代的写法,同样也是动态规划的方法求解这个问题。对于x > 1 and y > 1的点来说,它们的路径就是(x-1, y) + (x, y-1),而对于边界的点来说,路径为(x-1, y) or (x, y-1)。对与1的障碍来说,路径为0,那么我们很容易写出下面的代码

class Solution:
    def uniquePathsWithObstacles(self, obstacleGrid):
        """
        :type obstacleGrid: List[List[int]]
        :rtype: int
        """
        m, n = len(obstacleGrid), len(obstacleGrid[0])
        mem = [[0]*n for _ in range(m)]
        mem[0][0] = 1
        
        for i in range(m):
            for j in range(n):
                if obstacleGrid[i][j] == 0:
                    if j:
                        mem[i][j] += mem[i][j - 1]
                    if i:
                        mem[i][j] += mem[i - 1][j]
                else:
                    mem[i][j] = 0
        
        return mem[m - 1][n - 1]  

对于这个问题最好的解法是自底向上找路径,也就是从终点出发寻找起始点。所以我们对上面递归的过程稍加修改即可

class Solution:
    def uniquePathsWithObstacles(self, obstacleGrid):
        """
        :type obstacleGrid: List[List[int]]
        :rtype: int
        """
        m, n = len(obstacleGrid), len(obstacleGrid[0])
        if not obstacleGrid or obstacleGrid[0][0]:# modify
            return 0

        return self._obstacleGrid(obstacleGrid, m - 1, n - 1)

    def _obstacleGrid(self, grid, m, n):
        if m == 0 and n == 0:
            return 1

        if m < 0 or n < 0 or grid[m][n] == 1:
            return 0

        return self._obstacleGrid(grid, m - 1, n) + self._obstacleGrid(grid, m, n - 1)

同样这个问题我们也可以使用记忆化搜索的方式

class Solution:
    def uniquePathsWithObstacles(self, obstacleGrid):
        """
        :type obstacleGrid: List[List[int]]
        :rtype: int
        """

        if not obstacleGrid or obstacleGrid[0][0]:# modify
            return 0

        m, n = len(obstacleGrid), len(obstacleGrid[0])
        mem = [[None]*n for _ in range(m)]

        return self._obstacleGrid(obstacleGrid, m, n, m - 1, n - 1, mem)

    def _obstacleGrid(self, grid, m, n, row, col, mem):
        if row == 0 and col == 0:
            return 1

        if row < 0 or col < 0 or grid[row][col] == 1:
            return 0

        if row < m and col < n and mem[row][col] != None:
            return mem[row][col]

        mem[row][col] = self._obstacleGrid(grid, m, n, row - 1, col, mem) +\
                     self._obstacleGrid(grid, m, n, row, col - 1, mem)
        
        return mem[row][col]

这里要注意的是上面这个问题实现的细节

if row < 0 or col < 0 or grid[row][col] == 1:
    return 0

if row < m and col < n and mem[row][col]:
    return mem[row][col]

这两个判断的顺序不能做调整,为什么?因为-1python中是有意义的,如果调换的话,-1会进入第一个判断中,实际上我们希望返回0,显然同我们希望的不符。同样的我们也可以和前面一样,通过dict去实现

class Solution:
    def uniquePathsWithObstacles(self, obstacleGrid):
        """
        :type obstacleGrid: List[List[int]]
        :rtype: int
        """

        if not obstacleGrid or obstacleGrid[0][0]:# modify
            return 0

        m, n = len(obstacleGrid), len(obstacleGrid[0])
        mem = dict()

        return self._obstacleGrid(obstacleGrid, m, n, m - 1, n - 1, mem)

    def _obstacleGrid(self, grid, m, n, row, col, mem):
        tmp = '{}, {}'.format(row, col)
        if tmp in mem:
            return mem[tmp]

        if row == 0 and col == 0:
            return 1

        if row < 0 or col < 0 or grid[row][col] == 1:
            return 0

        mem[tmp] = self._obstacleGrid(grid, m, n, row - 1, col, mem) +\
                     self._obstacleGrid(grid, m, n, row, col - 1, mem)
        
        return mem[tmp]

同样的,我们也可以通过相同的手法,写出递归的算法

class Solution:
    def uniquePathsWithObstacles(self, obstacleGrid):
        """
        :type obstacleGrid: List[List[int]]
        :rtype: int
        """
        m, n = len(obstacleGrid), len(obstacleGrid[0])
        mem = [[0]*n for _ in range(m)]

        for i in range(m):
            for j in range(n):
                if not obstacleGrid[i][j]:
                    if i or j:
                        mem[i][j] = mem[i - 1][j] + mem[i][j - 1]
                    else:
                        mem[i][j] = 1

        return mem[-1][-1]

终于这个问题被我们彻底的解决了。o( ̄▽ ̄)o

我将该问题的其他语言版本添加到了我的GitHub Leetcode

如有问题,希望大家指出!!!

猜你喜欢

转载自blog.csdn.net/qq_17550379/article/details/82826800