LeetCode 62 比動態規劃更好的做法

今天是LeetCode專題第36篇文章,我們一起來看下LeetCode的62題,Unique Paths。

題意

其實這是一道老掉牙的題目了,我在高中信息競賽的選拔考試上就見過這題。可想而知它有多古老,或者說多經典吧。一般來說能夠流傳幾十年的算法題,一定是經典中的經典。下面我們就來看下它的題意。

這題的題意很簡單,給定一個矩形的迷宮,左上角有一個機器人,右下角是目的地。這個機器人只能向下走或者是向右走,請問這個機器人走到目的地的路徑一共有多少種?

這題很良心地給定了條件,矩形的長和寬都不超過100.

樣例

Input: m = 3, n = 2
Output: 3
Explanation:
From the top-left corner, there are a total of 3 ways to reach the bottom-right corner:
1. Right -> Right -> Down
2. Right -> Down -> Right
3. Down -> Right -> Right
Input: m = 7, n = 3
Output: 28

解法

在我寫題解的時候,我突然想起來上一次見到它好像是在某個綜藝節目當中。它被作爲一道智力題來考一些明星嘉賓,好像一衆明星裏面,只有關曉彤做了出來。。。

它作爲智力題有一個標準的模板式解法:

對於圖中的C點來說,從起點通往它的路徑數量等於通往A點和B點路徑的和。

這個結論我們很多人都知道,因爲C點只有兩個來源,一個是A點一個是B點。它既可以從A點來,也可以從B點來,所以應該是一個加和的關係。

這當然是沒錯的,但不知道大家從這個過程當中有沒有什麼感悟。C點的上游是A點和B點,也就是說C狀態是由A狀態或者是B狀態轉移到的。這不就是一個動態規劃算法嗎?

我們用dp記錄每一個位置的答案的話,那麼可以很輕鬆地寫出狀態轉移方程:

dp[i][j] = dp[i-1][j] + dp[i][j-1]

我們用代碼把這個方程實現就能解出問題了。

class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        dp = [[0 for _ in range(n+2)] for _ in range(m+2)]
        
        dp[0][1] = 1
        
        for i in range(1, m+1):
            # 特殊處理第一列,因爲第一列只有1種
            dp[i][1] = dp[i-1][1]
            for j in range(2, n+1):
                dp[i][j] = dp[i-1][j] + dp[i][j-1]
                
        return dp[m][n]

one more solution

如果只有上面這一種解法,那麼這道題完全沒有說的必要,也太簡單了。這道題還有另外一種解法,會更加簡單

在上面的解法當中,我們是把這個問題當成了算法題來解決的,使用動態規劃算法進行建模,適配題目來解決它。但如果我們換個思路,完全可以把它當做數學問題來解。

我們來分析一下問題,機器人要從左上角走到右下角,地圖是沒有缺陷的,所有點都可以到達。由於機器人沒辦法走回頭路,也就是說機器人在通往終點的過程當中走過的路程是確定的。也就是要走n-1條橫邊和m-1條豎邊。

邊的總數和種類都確定了,其實這個問題可以轉化一下。我們把走的橫邊看成是白球,走的豎邊看成是黑球,那麼這道題其實就可以轉化成,我們有n-1個白球,m-1個黑球,現在把它們排成一排,一共有多少種方法?

這個是小學的組合數學問題,我們要從整體的n+m-2個物體當中,選出n-1個,那麼顯然答案就是:Cn+m2n1C_{n+m-2}^{n-1}

不過,雖然我們用一個式子就表達了,但是要求解這個組合數,還是需要通過循環的。我們把它轉化成:

Cn+m2n1=(n+m2)(n+m3)...(m1)(n1)!C_{n+m-2}^{n-1}=\frac{(n+m-2)*(n+m-3)...(m-1)}{(n-1)!}

接着我們用循環求解即可。

class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        ret = 1
        
        for i in range(1, n):
            ret = ret * (n+m-1-i) // i
        return ret

相比於上面的做法更加簡短,雖然兩者看起來都是O(n)O(n)的算法,好像差別不大。但是每個數的階乘和組合數都是可以預處理的,在頻繁求解的場景下,顯然要比動態規劃算法更快。

結尾

這道題就算是講完了,大家會發現LeetCode過了50題之後,涉及到新的算法明顯變少了。畢竟LeetCode並不是高強度的算法平臺,主打的還是算法入門。這也限制了它的難度無法和codeforces、topcoder這種主打算法競賽的平臺相比。所以很多人刷到一百題左右的時候,就會覺得沒意思。

感覺很多題目大同小異,新鮮感和正反饋都變少了。這個時候就需要我們把問題做精,多去思考和求解最完美的解法。即使是爲了應付面試而刷題,這也是很有必要的。高水平的面試官往往都不會出LeetCode上的原題,都會加一些變化來考察選手,甚至有些大牛會出原創題。光摸清套路是不夠的,我們還需要更加深入的理解。

套用一句電影臺詞作爲結尾:we need to go deeper.

今天的文章就到這裏,原創不易,掃碼關注我,獲取更多精彩文章。

在這裏插入圖片描述

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