1. 程式人生 > >LeetCode22 生成所有括號對

LeetCode22 生成所有括號對

本文始發於個人公眾號:TechFlow,原創不易,求個關注


連結

Generate Parentheses


難度

Medium


描述


Given n pairs of parentheses, write a function to generate all combinations
of well-formed parentheses.

給定n對括號,要求返回所有這些括號組成的不同的合法的字串

For example, given n = 3, a solution set is:

[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]


題解


這道題目非常有意思,解法也很多,還是老規矩,我們先由易到難,先從最簡單的方法開始入手。

我們來簡單分析一下題目,n個括號對意味著字串的長度是2n,我們利用排列組合可以計算出,所有的組合種數一共有\(C_{2n}^n\)種。算一下會知道,這個數是很大的,也就是說我們哪怕一開始就知道答案,把答案遍歷一遍也會有很高的耗時,所以這道題對於時間複雜度的要求應該不會很高。


暴力


能想到最簡單的方法,當然是暴力,不要看不起這個樸素的演算法,很多時候靈感都是從暴力當中獲取的。但是這道題暴力不太容易寫,因為會有一種無從入手的感覺,我們知道要暴力,但是並不知道應該怎樣暴力。這道題不存在可以直接列舉的樸素元素,必須要我們拐個彎才行。

怎麼拐彎呢,其實答案我剛才已經說出來了。n個括號對,也就是說一共2n個字元,我們可以列舉n個'('分別放在什麼位置,剩下的自然就是')'了。看起來很有道理,但是有一個問題,就是這個思路並沒有辦法通過迴圈直接實現。這其實已經進化成了一個搜尋問題了,我們要搜尋所有可以擺放括號的可能性。

如果你能從暴力方法跳躍到搜尋問題,那麼說明你離寫出程式碼已經很接近了。如果不行,那麼我建議你花點時間去學習一下搜尋演算法專題。

對於搜尋問題而言,這已經很簡單了,我們搜尋的空間是明確的,2n個位置,搜尋的內容,對於每個位置我們可以擺放'('也可以擺放')'。那麼程式碼自然而然呼之欲出:

def dfs(pos, left, right, n, ret, cur_str):
    """
    pos: 當前列舉的位置
    left: 已經放置的左括號的數量
    right: 已經放置的右括號的數量
    n: 括號的數量
    ret: 放置答案的陣列
    cur_str: 當前的字串
    """
    if pos == 2*n:
        ret.append(cur_str)
        return 
    if left < n:
        dfs(pos+1, left+1, right, n, ret, cur_str+'(')
    if right < n:
        dfs(pos+1, left, right+1, n, ret, cur_str+')')

這個程式遍歷執行之後還沒有結束,我們還需要判斷生成出來的括號是否合法,也就是說括號需要匹配上。我們可以用一個棧來判斷括號是否能夠匹配,比如我們遇見左括號就進棧,遇見右括號則判斷棧頂,如果棧頂是左括號,那麼棧頂的左括號出棧,否則則入棧,最後判斷棧是否為空。這個演算法實現當然不難,但是如果你仔細去想了,你會發現完全沒有必要用棧,因為如果我們遇到右括號的時候,棧頂不為左括號,那麼一定最後是無法匹配的。因為後面出現的左括號不能匹配前面出現的右括號,正所謂往者不可追就是這個道理。【狗頭】


優化


我們來思考一個問題:什麼情況會出現右括號遇不到左括號呢?只有一種情況,就是當前出現右括號的個數超過了左括號,也就是說我們遍歷一下字串,如果中途出現右括號數量超過左括號的情況,那麼就說明這個字串是非法的。看起來沒毛病對吧,但是有問題,我們為什麼不在列舉的時候就判斷呢,如果左括號放入的數量已經等於右括號了,那麼就不往裡防止右括號,這樣不就可以保證搜尋到的一定是合法的字串嗎?

如果你能想到這一層,說明你對搜尋的理解已經很不錯了。我們看一下改動之後的程式碼:

def dfs(pos, left, right, n, ret, cur_str):
    """
    pos: 當前列舉的位置
    left: 已經放置的左括號的數量
    right: 已經放置的右括號的數量
    n: 括號的數量
    ret: 放置答案的陣列
    cur_str: 當前的字串
    """
    if pos == 2*n:
        ret.append(cur_str)
        return 
    if left < n:
        dfs(pos+1, left+1, right, n, ret, cur_str+'(')
    if right < n and right < left:
        dfs(pos+1, left, right+1, n, ret, cur_str+')')

大部分程式碼都沒有變化,只是在right < n後面加入了一個right < left這個條件。看似只有一個條件,但是這個條件起到的作用至關重要。整個演算法的效率有了質的提升,實際上這也是效率最高的演算法。


構造


上面的方案在LeetCode官方當中都有收入,也是比較常規的解法,下面要介紹的方法是我的原創,我個人感覺也比較有意思,分享給大家。

在之前的文章當中我們介紹過分治法,分治法的核心是將一個看似無法求解的大問題,分解成比較容易解決的小問題,最後加以解決。這道題當中,我們直接求n時的解法是比較困難的,沒辦法直接獲得,我們能不能也試著使用分治的方法來解決呢?

我們來觀察一下資料,當n=1的時候,很簡單,結果是(),只有這一種。當n=2呢?有兩種,分別是(())和()(),當n=3呢?有5種:((())), ()(()), ()()(), (()()), (())()。這當中有沒有規律呢?

我們用solution(n)表示n對應的解法,那麼我們可以寫出solution(n)對應的公式:

\[solution(n) = \sum_{i=1}^{n-1} solution(i)+solution(n-i) + ( + solution(n-1) + )\]

上面這個式子有點像是動態規劃的狀態轉移方程,雖然不完全一樣,但是大概是那麼回事。也就是說我們可以用比答案規模小的答案組裝成現在的答案。比如n=3時的答案,等於n=2時的答案和n=1時答案的拼接。

比如: solution(1) + solution(2) 可以得到: ()()()和()(()),solution(2) + solution(1)可以得到 ()()()和(())()。但是還有一種答案無法通過拼接得到就是( solution(2) )。也就是說在solution(2)的答案外面包一層括號。那為什麼不用考慮solution(1)的答案外面包兩層括號呢?答案很簡單,因為solution(2)已經包括了這樣的情況,所以我們只用往下考慮一層。

不過還沒有結束,還有一點小問題,就是這樣得到的答案可能會有重複,所以我們需要去重,利用set我們可以很簡單做到這點,讓我們一起來看程式碼:

class Solution:
            
    def generateParenthesis(self, n: int) -> List[str]:
        solutionMap = {}
        # 記錄下n=0和1時的答案
        solutionMap[0] = set([""])
        solutionMap[1] = set(["()"])

        # 遍歷小於n的所有長度
        for i in range(2, n+1):
            cur = set()
            # 遍歷小於n的所有長度
            for j in range(1, i):
                # 構造答案
                ans1 = solutionMap[j]
                ans2 = solutionMap[i-j]
                for s in ans1:
                    for t in ans2:
                        cur.add(s + t)
            # 構造 ( solution(n-1) )這種答案
            for s in solutionMap[i-1]:
                cur.add("(" + s + ")")
            solutionMap[i] = cur
        return list(solutionMap[n])

在C++當中,這兩種方法的效率差不多,但是使用Python的話,構造的方法要更快一些。和搜尋這種方法相比,搜尋是不知道答案去搜尋答案,而構造法是知道答案大概長什麼樣子,依據一定的規則生產答案。可以說是兩種不同思路的解法,也是我本人很喜歡這道題的原因。

這道題的程式碼都不長,但是思路挺有意思,希望大家會喜歡。

今天的文章就是這些,如果覺得有所收穫,請順手掃碼點個關注吧,你們的舉手之勞對我來說很重要。

相關推薦

LeetCode22 生成所有括號

本文始發於個人公眾號:TechFlow,原創不易,求個關注 連結 Generate Parentheses 難度 Medium 描述 Given n pairs of parentheses, write a function to generate all combinations of well-

給定n括號,編寫一個函式來生成正確括號所有組合。

本題源自leetcode ----------------------------------------------------------------------------------------------- 思路:  1  用回溯法。用變數m 表示左括號的數量。n

n個括號所有可能情況

括號 main color 思路 出棧 gin col r+ div 所有可能情況的數量為卡特蘭數。故求所有可能的出棧情況與此類似。 思路: 若左括號沒全插入,則插入左括號; 若已插入左括號數比已插入右括號數多,則插入右括號; 1 #include<st

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

核心是必須要先有一個左括號才能給字串新增括號,且無論何時右括號的個數一定要小於等於左括號的個數 而且函式引數最好不要使用引用,方便臨時變數的賦值。 C++程式碼如下 class Solution { public: vector<string> generateP

22.生成所有括號組合

Generate Parentheses 問題描述: Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses

[Leetcode] valid parentheses 有效括號

true class mine int min () etc strong bracket Given a string containing just the characters‘(‘,‘)‘,‘{‘,‘}‘,‘[‘and‘]‘, determine if the in

在 n 道題目中挑選一些使得所有題目的掌握情況不超過一半。

als main equal 超過一半 %d each amp preview 掌握 Snark and Philip are preparing the problemset for the upcoming pre-qualification round for se

IntelliJ IDEA設置代碼括號齊方式

har size pop class post 括號 成功 tar 圖片 IntelliJ IDEA設置代碼括號對齊方式 IntelliJ IDEA默認的對齊方式如下:括號跟函數名在一行 想改為括號獨自占一行,如下: 配置方式如下:File

hibernate框架學習筆記4:主鍵生成策略、象狀態

alt rri gen 線程安全 理論 微軟 unit conf lose 創建一個實體類: package domain; public class Customer { private Long cust_id; private Stri

ssl(https)介紹、實驗環境生成密鑰、nginx配置SSL、https

ssl nginx配置ssl ssl介紹 https 生成SSL密鑰對 ssl原理 http與https區別 http默認端口為80,https默認端口為443;http傳輸數據為明文,https傳輸數據是加密的; http是HTTP協議運行在TCP之上。所有傳輸的內容都是明文,客戶端

Intellij IDEA 生成返回值象快捷鍵

不必要 -- xtra style 需要 實現 alt ctr ext 在編寫一行JAVA語句時,有返回值的方法已經決定了返回對象的類型和泛型類型,我們只需要給這個對象起個名字就行。 如果使用快捷鍵生成這個返回值,我們就可以減少不必要的打字和思考,專註於過程的實現。 步驟:

nginx負載均衡、nginx ssl原理及生成密鑰、nginx配制ssl

alt self. 加密傳輸 remote cat nginx ssl 之間 PE www. 1、nginx負載均衡 新建一個文件:vim /usr/local/nginx/conf/vhost/load.conf寫入: upstream abc_com{ ip_ha

Java之生成Pdf並Pdf內容操作

enc images sub als tar 應用 throw mave add 雖說網上有很多可以在線導出Pdf或者word或者轉成png等格式的工具,但是我覺得還是得了解知道是怎麽實現的。一來,在線免費轉換工具,是有容量限制的,達到一定的容量時,是不能成功導出的;二來,

【資料結構】所有頂點的最短路徑 Floyd演算法

所有頂點對的最短路徑問題是指:對於給定的有向圖G=(V,E),求任意一對頂點之間的最短路徑。 可以求解得到的  的遞推公式: #include <stdio.h> #include <stdlib.h> const int FINI

openssl生成祕鑰

openssl genrsa -out pri.pem 1024 openssl rsa -in pri.pem -out pub.pem -pubout 這樣就生成祕鑰對了,其中pri.pem是私鑰,pub.pem是公鑰 比如當前目錄有一個檔案叫test.txt 加密test.txt

python3 RSA演算法生成祕鑰、檔案加密解密

RSA檔案加密解密生成祕鑰對檔案加密檔案解密 生成祕鑰對 @staticmethod def create_rsa_keys(code='nooneknows'): # 生成 2048 位的 RSA 金鑰 key

LeetCode:22. Generate Parentheses(生成匹配括號

Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses. For example, given n = 3

MTGAN:通過多工三元生成對抗性網路說話人進行驗證

MTGAN: Speaker Verification through Multitasking Triplet Generative Adversarial Networks MTGAN:通過多工三元生成對抗性網路對說話人進行驗證 摘要 在本文中,我們提出了一種增強的三元組方法,它通過

使用棧實現可配置的括號齊以及棧的實現原理

丟擲問題:校驗字串中括號是否對應,並可以根據配置修改需要匹配的成對字元。 例:()()(())  OK ()[]{}{([])}  OK ((())]  NO 對應則返回true,若不對應則返回false C#程式碼如下(與java程式碼差距不大),使用棧(新進後出

Floyd-Warshall 所有結點的最短路徑演算法

以下程式碼僅支援結點是順序的,比如輸入5個結點,結點的編號只能是1到5,輸入順序可以不一致。 動態規劃真的簡潔,三個 for 把這麼複雜的東西就整理好了。 遞推公式:**d[i][j] = min ( d[i][j] , d[i][k] + d[k][j] )