1. 程式人生 > 其它 >《每日一題》62. Unique Paths 不同路徑

《每日一題》62. Unique Paths 不同路徑

技術標籤:# LeetCode

一個機器人位於一個 m x n網格的左上角 (起始點在下圖中標記為 “Start” )。

機器人每次只能向下或者向右移動一步。機器人試圖達到網格的右下角(在下圖中標記為 “Finish” )。

問總共有多少條不同的路徑?

示例 1:

輸入:m = 3, n = 7
輸出:28

示例 2:

輸入:m = 3, n = 2
輸出:3
解釋:
從左上角開始,總共有 3 條路徑可以到達右下角。
1. 向右 -> 向右 -> 向下
2. 向右 -> 向下 -> 向右
3. 向下 -> 向右 -> 向右

示例 3:

輸入:
m = 7, n = 3 輸出:28

示例 4:

輸入:m = 3, n = 3
輸出:6

提示:

  • 1 <= m, n <= 100
  • 題目資料保證答案小於等於 2 * 109

DFS

今天這題那麼簡單???直接深搜就能出結果???不可能吧。

Python

class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        def dfs(x: int, y: int):
            # print(f"x = {x}, y = {y}")
            if x ==
m - 1 and y == n - 1: return 1 if x > m - 1 or y > n - 1: return 0 return dfs(x + 1, y) + dfs(x, y + 1) return dfs(0, 0)

果然超時了,沒過,DFS不行那就得DP了。

DP

我們用 f(i,j)f(i, j)f(i,j) 表示從左上角走到 (i,j)(i, j)(i,j) 的路徑數量,其中 iiijjj 的範圍分別是 [0,m)[0, m)

[0,m)[0,n)[0, n)[0,n)

由於我們每一步只能從向下或者向右移動一步,因此要想走到 (i,j)(i, j)(i,j),如果向下走一步,那麼會從 (i−1,j)(i-1, j)(i1,j) 走過來;如果向右走一步,那麼會從 (i,j−1)(i, j-1)(i,j1) 走過來。因此我們可以寫出動態規劃轉移方程:

f(i,j)=f(i−1,j)+f(i,j−1)f(i, j) = f(i-1, j) + f(i, j-1) f(i,j)=f(i1,j)+f(i,j1)

需要注意的是,如果 i=0i=0i=0,那麼 f(i−1,j)f(i-1,j)f(i1,j) 並不是一個滿足要求的狀態,我們需要忽略這一項;同理,如果 j=0j=0j=0,那麼 f(i,j−1)f(i,j-1)f(i,j1) 並不是一個滿足要求的狀態,我們需要忽略這一項。

初始條件為 f(0,0)=1f(0,0)=1f(0,0)=1,即從左上角走到左上角有一種方法。

最終的答案即為 f(m−1,n−1)f(m-1,n-1)f(m1,n1)

Python

class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        dp = [[1] * n] + [[1] + [0] * (n - 1) for _ in range(m - 1)]
        for i in range(1, m):
            for j in range(1, n):
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
        return dp[m - 1][n - 1]

複雜度分析

  • 時間複雜度:O(mn)O(mn)O(mn)

  • 空間複雜度:O(mn)O(mn)O(mn),即為儲存所有狀態需要的空間。注意到 f(i,j)f(i, j)f(i,j) 僅與第 iii 行和第 i−1i-1i1 行的狀態有關,因此我們可以使用滾動陣列代替程式碼中的二維陣列,使空間複雜度降低為 O(n)O(n)O(n)。此外,由於我們交換行列的值並不會對答案產生影響,因此我們總可以通過交換 mmmnnn 使得 m≤nm \leq nmn,這樣空間複雜度降低至 O(min⁡(m,n))O(\min(m, n))O(min(m,n))

滾動陣列實現

class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        dp = [[1] * n] + [[1] + [0] * (n - 1)]
        for i in range(1, m):
            for j in range(1, n):
                dp[i % 2][j] = dp[abs(i % 2 - 1)][j] + dp[i % 2][j - 1]
        return dp[abs(m % 2 - 1)][n - 1]

組合數學

從左上角到右下角的過程中,我們需要移動 m+n−2m+n-2m+n2 次,其中有 m−1m-1m1 次向下移動,n−1n-1n1 次向右移動。因此路徑的總數,就等於從 m+n−2m+n-2m+n2 次移動中選擇 m−1m-1m1 次向下移動的方案數,即組合數:

C m + n − 2 m − 1 = ( m + n − 2 m − 1 ) = ( m + n − 2 ) ! ( m − 1 ) ! ( n − 1 ) ! C_{m+n-2}^{m-1}=\begin{pmatrix} m+n-2\\m-1\end{pmatrix}=\frac{(m+n-2)!}{(m-1)!(n-1)!} Cm+n2m1=(m+n2m1)=(m1)!(n1)!(m+n2)!

Python

class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        return comb(m + n - 2, n - 1)

複雜度分析

  • 時間複雜度:O(m)O(m)O(m)。由於我們交換行列的值並不會對答案產生影響,因此我們總可以通過交換 mmmnnn 使得 m≤nm \leq nmn,這樣空間複雜度降低至 O(min⁡(m,n))O(\min(m, n))O(min(m,n))

  • 空間複雜度:O(1)O(1)O(1)