leetcode 第62題 不同路徑, 第63題 不同路徑 II, 第64題,最小路徑和(python解法)
leetcode 第62題 不同路徑, 第63題 不同路徑 II, 第64題,最小路徑和 (python解法)
問題解析
最近在寫動態規劃的題目,刷題時看到這三道題,覺得很有意思。這三題的內容基本差不多,可以看成時迷宮問題(但是要簡單的多,因為可以移動的方向只有兩個,向下或向右),所以用動態規劃解比較方便。
第62題: 不同路徑
它給定兩個整數代表移動網格的大小,所以我們可以先利用生成一個與網格大小相等的二維陣列,這個陣列每一個數是索引代表位置的路徑數。由於只能向下或者向右移動,所以對於陣列的最後一列上所有的位置,都只能向下移動,因此這些位置的數都先設為1。同理,由於只能向右移動,那麼最後一行的數也全部設為1(如下圖所示)。
設定好這些後,下面開始遍歷,從倒數第二行和倒數第二列開始,前面說過某個位置的路徑數等於下面位置與右邊位置路徑和相加,所以可以寫出狀態轉移方程為dp[i][j] = dp[i-1][j] + dp[i][j-1]。由這個轉移方程就可以寫出所有的位置的路徑數了。最後的結果如下:
最後只要輸出dp[0][0],就OK了。
擴充套件
上面這個是一種常規解法,用的是動態規劃,下面介紹一種更為直觀的思路,利用排列組合來解題。
首先,有一點我們是可以確定的,那就是機器人從起點到終點總共要走m+n-2步,其中m-1步向左,n-1步向下(m代表列數,n代表行數)。那麼問題就可以簡化成從m+n-2步中挑選出m-1步出來,共有多少種挑選法?(可以類比成m+n-2個人排成一排,從中挑選出m-1個人出來的方法數)
第63題:不同路徑 II
這一題相比較上一題加了一個條件,即在陣列中設定了障礙物,存在障礙物的位置不能移動,也就是所有可行的路線在這裡中斷。首先,先看兩個比較特殊的情況,那就是終點或者起點存在障礙物,這樣話就不用計算而直接返回零。
接下來看看最後一列和最後一行。最後一列,如果某個位置上存在障礙物,由於這一列只能向下移動,所以該位置向上所有的位置都不能到達終點,全部設為零,其它的能夠到達的先設為零。最後一行也是如此。
其它的位置在遍歷時分兩種情況:一種是當前位置上存在障礙物,那麼就直接設為零,即無法到達終點。第二種是當前位置沒有障礙物,那麼就和第62題一樣的操作。所以狀態轉移方程為:
假設網格中存在三個障礙物,如圖所示,最後一行和最後一列的路線就可以寫出來。
接下來就可以開始遍歷了,下面是最後的結果圖:
恰好最後的結果等於零。
這一題我們可以不開闢一個新的空間來儲存dp這個二維陣列,而直接在題目所給的陣列上操作。這可能有點繞,因為陣列中1代表障礙物。在操作原陣列最後一行和最後一列時,如果出現1,那麼就將1上面的位置或者前面的位置(包括當前位置)全部設為零(說明這些位置到達終點的路徑數是零)。其餘的位置設為1。而在遍歷陣列的過程中,如果遇到1,說明此處由障礙物,直接改為0。
第64題 最小路徑和
其實第63題可以看成是64題一個小小的變形。我們也可以不用開闢新的空間,直接在原陣列上操作。由於只能向下或向右,所以陣列的最後一行只有一個移動方向,所以最後一行每個位置的路徑和就等於當前位置的數直接加上後面一個位置的路徑和。最後一列也是這樣。
從倒數第二行和倒數第二列開始,每個位置上的數要加上下面的位置和右邊的位置中較小的數,這樣就實現了最小的路徑和,狀態轉移方程可以寫為:dp[i][j] += min(dp[i-1][j], dp[i][j-1])。這樣遍歷到最後,dp[0][0]代表的就是最小的路徑和。
原始碼
第62題: 不同路徑
from math import factorial
class Solution:
def uniquePaths(self, m, n):
"""
:type m: int
:type n: int
:rtype: int
"""
# 中規中矩,動態規劃
# dp = [[1 if i == m-1 or j == n-1 else 0 for i in range(m)] for j in range(n)]
# print(dp)
# for i in range(m-2, -1, -1):
# for j in range(n-2, -1, -1):
# print(i, j)
# dp[j][i] = dp[j+1][i] + dp[j][i+1]
# print(dp)
# return dp[0][0]
# 注意,前方高能。使用排列組合。
return int(factorial(m + n - 2) / factorial(m -1) / factorial(n-1))
第63題:不同路徑 II
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid):
"""
:type obstacleGrid: List[List[int]]
:rtype: int
"""
if obstacleGrid[-1][-1] or obstacleGrid[0][0]: # 最特殊的情況,終點或起點被阻擋,那麼總路線等於零
return 0
m = len(obstacleGrid)
n = len(obstacleGrid[0])
for i in range(m-1, -1, -1): # 檢查最後一列,如果出現1,則該位置上面的所有的位置都設為零
if obstacleGrid[i][n-1] == 0:
obstacleGrid[i][n-1] = 1
else:
for i1 in range(i, -1, -1):
obstacleGrid[i1][n-1] = 0
break
print(obstacleGrid)
for j in range(n-2, -1, -1): # 檢查最後一行,如果出現1,則該位置前面的所有的位置都是無法通過的,所以路線為1
if obstacleGrid[m-1][j] == 0:
obstacleGrid[m-1][j] = 1
else:
for j1 in range(j, -1, -1):
obstacleGrid[m-1][j1] = 0
break
print(obstacleGrid)
for i in range(m-2, -1, -1):
for j in range(n-2, -1, -1):
obstacleGrid[i][j] = 0 if obstacleGrid[i][j] == 1 else obstacleGrid[i+1][j] + obstacleGrid[i][j+1]
print(obstacleGrid)
return obstacleGrid[0][0]
第64題 最小路徑和
class Solution:
def minPathSum(self, grid):
"""
:type grid: List[List[int]]
:rtype: int
"""
m = len(grid)
n = len(grid[0])
for i in range(m-2, -1, -1):
grid[i][n-1] += grid[i+1][n-1]
for j in range(n-2, -1, -1):
grid[m-1][j] += grid[m-1][j+1]
for i in range(m-2, -1, -1):
for j in range(n-2, -1, -1):
grid[i][j] += min(grid[i+1][j], grid[i][j+1])
print(grid)
return grid[0][0]