[leetcode 63] 不同路徑 II(Python 動態規劃+滾動數組優化)

題目描述

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

解題思路

我們用 f(i,j)f(i, j) 來表示從座標 (0,0)(0, 0) 到座標 (i,j)(i, j) 的路徑總數,u(i,j)u(i, j) 表示座標 (i,j)(i, j) 是否可行,如果座標 (i,j)(i, j) 有障礙物,u(i,j)=0u(i, j) = 0,否則 u(i,j)=1u(i, j) = 1

因爲機器人每次只能向下或者向右移動一步,所以(0,0)(0, 0) 到座標 (i,j)(i, j) 的路徑總數的值只取決於從座標 (0,0)(0, 0) 到座標 (i1,j)(i - 1, j) 的路徑總數和從座標 (0,0)(0, 0) 到座標 (i,j1)(i, j - 1) 的路徑總數,即 f(i,j)f(i, j) 只能通過 f(i1,j)f(i - 1, j)f(i,j1)f(i, j - 1) 轉移得到。

當座標 (i,j)(i, j) 本身有障礙的時候:任何路徑都到到不了 f(i,j)f(i, j),此時 f(i,j)=0f(i, j) = 0
當座標 (i,j)(i, j) 沒有障礙的時候:如果座標 (i1,j)(i - 1, j) 沒有障礙,那麼就意味着從座標 (i1,j)(i - 1, j) 可以走到 (i,j)(i, j) ,即 (i1,j)(i - 1, j) 位置對 f(i,j)f(i, j) 的貢獻爲 f(i1,j)f(i - 1, j) ;同理,當座標 (i,j1)(i, j - 1) 沒有障礙的時候,(i,j1)(i, j - 1) 位置對 f(i,j)f(i, j) 的貢獻爲 f(i,j1)f(i, j - 1)

綜上所述,我們可以得到這樣的動態規劃轉移方程:
f(i,j)={0,u(i,j)=0f(i1,j)+f(i,j1),u(i,j)0 f(i, j)=\left\{\begin{aligned} 0, & u(i, j)=0 \\ f(i-1, j)+f(i, j-1), & u(i, j) \neq 0 \end{aligned}\right.
由於這裏 f(i,j)f(i, j) 只與 f(i1,j)f(i - 1, j)f(i,j1)f(i, j - 1) 相關,我們可以運用滾動數組思想把空間複雜度優化稱 O(m)O(m)

滾動數組思想是一種常見的動態規劃優化方法,在題目中已經多次使用到,例如「劍指 Offer 46. 把數字翻譯成字符串」、「70. 爬樓梯」等,當定義的狀態在動態規劃的轉移方程中只和某幾個狀態相關的時候,就可以考慮這種優化方法,目的是給空間複雜度「降維」。

- 複雜度分析

  • 時間複雜度:O(nm)O(nm),其中 nn 爲網格的行數,mm 爲網格的列數。我們只需要遍歷所有網格一次即可。
  • 空間複雜度:O(m)O(m)。利用滾動數組優化,我們可以只用 O(m)O(m) 大小的空間來記錄當前行的 ff 值。

代碼實現

class Solution(object):
    def uniquePathsWithObstacles(self, obstacleGrid):
        """
        :type obstacleGrid: List[List[int]]
        :rtype: int
        """
        m = len(obstacleGrid)
        n = len(obstacleGrid[0])
        dp = [1] + [0]*(n-1)
        for i in range(m):
            for j in range(n):
                if j == 0:
                    dp[j] = 0 if obstacleGrid[i][j] else dp[j]
                else:
                    dp[j] = 0 if obstacleGrid[i][j] else dp[j] + dp[j-1]
        return dp[-1]

Tips

怎麼想到用動態規劃來解決這個問題呢?我們需要從問題本身出發,尋找一些有用的信息,例如本題中:

  • (i,j)(i, j) 位置只能從 (i1,j)(i - 1, j)(i,j1)(i, j - 1) 走到,這樣的條件就是在告訴我們這裏轉移是 無後效性 的,f(i,j)f(i, j) 和任何的 f(i,j)(i>i,j>j)f(i', j')(i' > i, j' > j) 無關。
  • 動態規劃的題目分爲兩大類,一種是求最優解類,典型問題是揹包問題,另一種就是計數類,比如這裏的統計方案數的問題,它們都存在一定的遞推性質。前者的遞推性質還有一個名字,叫做 最優子結構 ——即當前問題的最優解取決於子問題的最優解,後者類似,當前問題的方案數取決於子問題的方案數。所以在遇到求方案數的問題時,我們可以往動態規劃的方向考慮。
    通常如果我們察覺到了這兩點要素,這個問題八成可以用動態規劃來解決。

根據官方題解總結。


Author:ChierAuthor: Chier

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