有效括號生成


  合法的括號匹配的問題之前已經講解過了,現在再看一個括號生成的題目。

題目描述

給出 n 代表生成括號的對數,請你寫出一個函數,使其能夠生成所有可能的並且有效的括號組合。

例如,給出 n = 3,生成結果爲:

[ “((()))”, “(()())”, “(())()”, “()(())”, “()()()” ]

題目分析

  既然之前已經分析過合法的括號判斷,那麼只要把n個左括號和n個右括號全排列,然後對排列的結果去做一個有效驗證,好了,這個問題解決了。但是仔細一分析,判斷一個括號是否有效,複雜度O(n)O(n),生成2n個括號字符的全排列,時間複雜度O(22n)O(2^{2n}),這很難辦啊。所以全排列的方式行不通。

深度搜索法

  全排列裏面有很多左括號和右括號數目不等的,可以直接排除了,所以浪費了很多判斷。我們可以通過回溯提前把不合理的字符排除掉。但是回溯法只能是逐漸的字符串變長,也就是深度優先搜索的原理。但是我們這個時候在每一次增加括號的時候,我們保證右括號不會超過當前的左括號數目即可。還要保證最終的括號數目,左括號和右括號的數目都是n。
  根據這個思想,我們可以去寫代碼了,我們記錄下當前左括號的數目,記錄下當前右括號的數目,如果左括號大於右括號,且左括號數目小於n,此時我們深度搜索的路徑包括兩條,添加左括號和右括號。如果左括號數目等於n了,那麼就只有一條路徑,如果左括號數目等於右括號數目了且不等於n,此時只能添加左括號。如果兩者數目相等,且等於n,則可以返回了,已經生成了一組合法的括號字符。整個算法就是一個深度遍歷,可以採用遞歸來實現。

python代碼
class Solution:
    def generateParenthesis(self,n):
        ans=[]
        self.max=n
        self.backtrack(ans, "", 0, 0)
        return ans
    def backtrack(self, ans, cur, left, right):
        if left==self.max:
            ans.append(cur +')' * (self.max - right))
            return
        if(left<self.max):
            self.backtrack(ans, cur +'(', left + 1, right)
        if right<left:
            self.backtrack(ans, cur +')', left, right + 1)

  可以看到,這種方式保證了左括號的數目始終多於右括號,這樣生成出來的一定是合法的括號字符串。因爲每次生成保證了都是合法的前綴,這個複雜度就大大的減少了。
  事實上,合法的括號數目,我們在卡特蘭數的應用分析了n個括號的合法種類數就是卡特蘭數列的第n項,我們大致可以根據卡特蘭數的性質,分析問題的規模。我們知道卡特蘭數的漸進上界是O(4nn)O\left ( \frac{4^n}{ \sqrt{n}} \right ),而每一個合法的序列,我們是通過O(n)O(n)次添加字符生成出來的。

動態規劃法

  既然這個問題和卡特蘭數相關,那麼一定可以根據卡特蘭數的特點,一定可以把這個問題轉換成以前所有狀態的窮舉。說的通俗一點就是,如果我們研究問題規模爲n,那麼問題規模之和爲n-1的兩兩組合一定可以用得上,如果這句話理解不了可以直接跳到下一段。
  我們爲了降低問題規模,可以考慮在問題規模爲n-1的情況上加一對括號,可是這個多出來的一對括號應該往哪加。顯然,我們可以把這個括號加在所有的合法匹配的括號之後。如果合法匹配數爲0的時候,我們就把括號加在了最前面,就是()+dpn1()+dp_{n-1},再者我們可以把括號加到一對合法括號後面,也是(dp1)+dpn2(dp_1)+dp_{n-2}依次類推,n-1個合法的括號總共可以分解出n-1種情況。每個情況分爲兩部分,前面一部分是dp_i,後面一部分是dp_n-1-i,其實也就是兩部分滿足下標之和爲n-1就行。
  據此我們可以寫出動態規劃的遞歸式。dpi="("+dpj+")"+dpij1, where j<idp_i="(" + dp_j + ")"+dp_{i-j-1}, \ where \ j<i。事實上,幾乎所有的卡特蘭數問題都可以寫成類似的遞推式,對所有兩兩組合爲n-1的情況然後窮舉,並做出一定的修改。大家可以記住這個結論,當然也可以寫成數學表達式,我直接寫成程序的表達式即可。下面就可以根據這個表達式寫代碼。只需要對dpjdp_jdpij1dp_{i-j-1}做個全排列即可。

python代碼
class Solution:
    # 動態規劃
    def generateParenthesis(self, n: int) -> List[str]:
        dp=[[] for i in range(n+1)]
        dp[0]=['']
        for i in range(1,n+1):
            for j in range(i):
                dp[i].extend(self.compent(dp[j],dp[i-j-1]))
        return dp[n]

    def compent(self, l, r):
    # 全排列代碼,當時只寫了一行,爲了大家好理解,特此重寫一份
        res=[]
        for s1 in l:
            for s2 in r:
                res.append('('+s1+')'+s2 )
        return res

  分析時間複雜度的時候大家需要注意一下,就是雖然這個代碼寫出來是4個for,但是這個代碼的時間複雜度不是O(n4)O(n^4),相比於上面的回溯法,其實時間複雜度的指數部分還是保持的,單是求問題規模等於n的情況,時間複雜度就已經達到了O(4nn)O\left ( \frac{4^n}{ \sqrt{n}} \right )

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