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