1. 程式人生 > >Leetcode 63:不同路徑 II(最詳細的解法!!!)

Leetcode 63:不同路徑 II(最詳細的解法!!!)

一個機器人位於一個 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

如有問題,希望大家指出!!!