LeetCode刷題——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 條不同的路徑:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右

來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/unique-paths-ii

思路

該題是上一題62. 不同路徑的升級版,多了一個障礙物而已。可以參照上一題的解題思路。

只要將經過障礙物的路徑數設成0即可。

代碼

代碼都是基於62. 不同路徑修改而來的。

遞歸

class Solution(object):
    def unique(self, obstacleGrid, m, n):
        # 如果遇到障礙物,返回0
        if obstacleGrid[n][m] == 1:
            return 0

		 # 如果到達了起點,就是一種解法
        if m == 0 and n == 0:
            return 1

        ways = 0

        # 如果可以向左回退一步
        if m >= 1:
            ways = self.unique(obstacleGrid, m - 1, n)
        # 如果可以向上回退一步
        if n >= 1:
            ways += self.unique(obstacleGrid, m, n - 1)

        return ways

    def uniquePathsWithObstacles(self, obstacleGrid):
        """
        :type obstacleGrid: List[List[int]]
        :rtype: int
        """
        # n行 m 列的列表
        n, m = len(obstacleGrid), len(obstacleGrid[0])

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

爲了能使用上一題的代碼,n, m = len(obstacleGrid), len(obstacleGrid[0])這裏求出了這個列表的行數與列數,並且傳入遞歸函數中。

if obstacleGrid[n][m] == 1:
	return 0

基本在遞歸函數中只加了這段代碼而已,非常簡單。

在這裏插入圖片描述

雖然思路是對的,但是遞歸的方式是比較低效的,這裏導致了計算超時。參閱LeetCode刷題之動態規劃思想,我們可以將其改成記憶化搜索的方式,解決重疊子問題。

記憶化搜索

dp = {}
class Solution(object):
    def unique(self, obstacleGrid, m, n):
        if (m,n) not in dp:
            # 如果遇到障礙物,返回0
            if obstacleGrid[n][m] == 1:
                dp[(m,n)] = 0
                return 0
                
        	# 如果到達了起點,就是一種解法
            if m == 0 and n == 0:
                dp[(m,n)] = 1
                return 1

            ways = 0

            # 如果可以向左回退一步
            if m >= 1:
                ways = self.unique(obstacleGrid, m - 1, n)
            # 如果可以向上回退一步
            if n >= 1:
                ways += self.unique(obstacleGrid, m, n - 1)

            dp[(m,n)] = ways

        return dp[(m,n)]

    def uniquePathsWithObstacles(self, obstacleGrid):
        # n行 m 列的列表
        n, m = len(obstacleGrid), len(obstacleGrid[0])

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

記憶化搜索的修改方式也很容易。

在這裏插入圖片描述
但是又說我記憶化搜索的代碼錯誤了,實際上進行測試時是對的。

在這裏插入圖片描述
不管,改成動態規劃。

動態規劃

class Solution(object):
    def uniquePathsWithObstacles(self, obstacleGrid):
        """
        :type obstacleGrid: List[List[int]]
        :rtype: int
        """
        # n行 m 列的列表
        n, m = len(obstacleGrid), len(obstacleGrid[0])

        # n行 m 列的列表
        dp = [[0] * m for _ in range(n)]

        #做一個小優化,如果起點就是障礙,那麼無解
        if obstacleGrid[0][0] == 1: 
            return 0
        else:
            dp[0][0] = 1

        # 先設定好邊界值,設置第一列的邊界值
        for i in range(1, n):
            # 如果當前格子不是是障礙 且上一個格子可達
            if obstacleGrid[i][0] != 1 and dp[i - 1][0] != 0 :
                dp[i][0] = 1
            # 否則當前格子不可達

        # 設置第一行的邊界值
        for j in range(1, m):
            if obstacleGrid[0][j] != 1 and dp[0][j - 1] != 0:
                dp[0][j] = 1

        for i in range(1, n):
            for j in range(1, m):
                # 如果當前格不是障礙,才能計算
                if obstacleGrid[i][j] != 1:
                    # dp[i][j] = 左邊一步加上面一步的結果
                    dp[i][j] = dp[i - 1][j] + dp[i][j - 1]

        return dp[-1][-1]

這裏設定邊界值的時候要麻煩一點。

在這裏插入圖片描述
如上圖所示,用"X"表示障礙物,如果障礙物出現在了邊界上,障礙物和右邊的格子都是不可達的。
考慮到這個情況就可以了。

#做一個小優化,如果起點就是障礙,那麼無解
if obstacleGrid[0][0] == 1: 
    return 0
else:
    dp[0][0] = 1

並且增加對起點的判斷,使得下面設定邊界值的代碼for循環的range都可以從1開始:

# 先設定好邊界值,設置第一列的邊界值
for i in range(1, n):
    # 如果當前格子不是是障礙 且上一個格子可達
    if obstacleGrid[i][0] != 1 and dp[i - 1][0] != 0 :
        dp[i][0] = 1
    # 否則當前格子不可達

# 設置第一行的邊界值
for j in range(1, m):
    if obstacleGrid[0][j] != 1 and dp[0][j - 1] != 0:
        dp[0][j] = 1

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章