回溯演算法最佳實踐 括號生成

2022-07-03 23:36:10 字數 3679 閱讀 5927

22.括號生成

-----------

括號問題可以簡單分成兩類,一類是前文寫過的 括號的合法性判斷 ,一類是合法括號的生成。對於括號合法性的判斷,主要是借助「棧」這種資料結構,而對於括號的生成,一般都要利用回溯遞迴的思想。

關於回溯演算法,我們前文寫過一篇 回溯演算法套路框架詳解 反響非常好,讀本文前應該讀過那篇文章,這樣你就能夠進一步了解回溯演算法的框架使用方法了。

回到正題,括號生成演算法是 leetcode 第 22 題,要求如下:

請你寫乙個演算法,輸入是乙個正整數n,輸出是n對兒括號的所有合法組合,函式簽名如下:

vectorgenerateparenthesis(int n);
比如說,輸入n=3,輸出為如下 5 個字串:

"((()))",

"(()())",

"(())()",

"()(())",

"()()()"

有關括號問題,你只要記住以下性質,思路就很容易想出來:

1、乙個「合法」括號組合的左括號數量一定等於右括號數量,這個很好理解

2、對於乙個「合法」的括號字串組合p,必然對於任何0 <= i < len(p)都有:子串p[0..i]中左括號的數量都大於或等於右括號的數量

如果不跟你說這個性質,可能不太容易發現,但是稍微想一下,其實很容易理解,因為從左往右算的話,肯定是左括號多嘛,到最後左右括號數量相等,說明這個括號組合是合法的。

反之,比如這個括號組合))((,前幾個子串都是右括號多於左括號,顯然不是合法的括號組合。

下面就來手把手實踐一下回溯演算法框架。

ps:我認真寫了 100 多篇原創,手把手刷 200 道力扣題目,全部發布在 labuladong的演算法小抄,持續更新。建議收藏,按照我的文章順序刷題,掌握各種演算法套路後投再入題海就如魚得水了。

明白了合法括號的性質,如何把這道題和回溯演算法扯上關係呢?

演算法輸入乙個整數n,讓你計算n對兒括號能組成幾種合法的括號組合,可以改寫成如下問題:

現在有2n個位置,每個位置可以放置字元(或者),組成的所有括號組合中,有多少個是合法的

這個命題和題目的意思完全是一樣的對吧,那麼我們先想想如何得到全部2^(2n)種組合,然後再根據我們剛才總結出的合法括號組合的性質篩選出合法的組合,不就完事兒了?

如何得到所有的組合呢?這就是標準的暴力窮舉回溯框架啊,我們前文 回溯演算法套路框架詳解 都總結過了:

result = 

def backtrack(路徑, 選擇列表):

if 滿足結束條件:

result.add(路徑)

return

for 選擇 in 選擇列表:

做選擇backtrack(路徑, 選擇列表)

撤銷選擇

那麼對於我們的需求,如何列印所有括號組合呢?套一下框架就出來了,偽碼如下:

void backtrack(int n, int i, string& track) 

// 對於每個位置可以是左括號或者右括號兩種選擇

for choice in ['(', ')']

}

那麼,現在能夠列印所有括號組合了,如何從它們中篩選出合法的括號組合呢?​很簡單,加幾個條件進行「剪枝」就行了。

對於2n個位置,必然有n個左括號,n個右括號,所以我們不是簡單的記錄窮舉位置i,而是left記錄還可以使用多少個左括號,用right記錄還可以使用多少個右括號,這樣就可以通過剛才總結的合法括號規律進行篩選了:

vectorgenerateparenthesis(int n) ;

// 記錄所有合法的括號組合

vectorres;

// 回溯過程中的路徑

string track;

// 可用的左括號和右括號數量初始化為 n

backtrack(n, n, track, res);

return res;

}// 可用的左括號數量為 left 個,可用的右括號數量為 rgiht 個

void backtrack(int left, int right,

string& track, vector& res)

// 嘗試放乙個左括號

track.push_back('('); // 選擇

backtrack(left - 1, right, track, res);

track.pop_back(); // 撤消選擇

// 嘗試放乙個右括號

track.push_back(')'); // 選擇

backtrack(left, right - 1, track, res);

track.pop_back(); ;// 撤消選擇

}

這樣,我們的演算法就完成了,演算法的複雜度是多少呢?這個比較難分析,對於遞迴相關的演算法,時間複雜度這樣計算(遞迴次數)*(遞迴函式本身的時間複雜度)

ps:我認真寫了 100 多篇原創,手把手刷 200 道力扣題目,全部發布在 labuladong的演算法小抄,持續更新。建議收藏,按照我的文章順序刷題,掌握各種演算法套路後投再入題海就如魚得水了。

backtrack就是我們的遞迴函式,其中沒有任何 for 迴圈**,所以遞迴函式本身的時間複雜度是 o(1),但關鍵是這個函式的遞迴次數是多少?換句話說,給定乙個nbacktrack函式遞迴被呼叫了多少次?

我們前面怎麼分析動態規劃演算法的遞迴次數的?主要是看「狀態」的個數對吧。其實回溯演算法和動態規劃的本質都是窮舉,只不過動態規劃存在「重疊子問題」可以優化,而回溯演算法不存在而已。

所以說這裡也可以用「狀態」這個概念,對於backtrack函式,狀態有三個,分別是left, right, track,這三個變數的所有組合個數就是backtrack函式的狀態個數(呼叫次數)。

leftright的組合好辦,他倆取值就是 0~n 嘛,組合起來也就n^2種而已;這個track的長度雖然取在 0~2n,但對於每乙個長度,它還有指數級的括號組合,這個是不好算的。

說了這麼多,就是想讓大家知道這個演算法的複雜度是指數級,而且不好算,這裡就不具體展開了,是 \(\frac}}\),有興趣的讀者可以搜尋一下「卡特蘭數」相關的知識了解一下這個複雜度是怎麼算的。

_____________

回溯演算法 括號生成

22.括號生成 思路 先用回溯演算法生成所有可能的括號,再判斷生成的括號是否為有效括號。對於有效括號的判斷,leetcode也有一道專門的題,用棧輔助就可以很簡單的解決。整體 也沒啥,就是回溯 有效括號的判斷 class solution def generateparenthesis self,n...

leetcode 生成括號(回溯演算法)

出處 生成括號 給出 n 代表生成括號的對數,請你寫出乙個函式,使其能夠生成所有可能的並且有效的括號組合。例如,給出 n 3,生成結果為 從題目尋找三要素 1 選擇 加左括號 加右括號 2 條件 左括號沒有用完 才可以加左括號 右括號數目小於左括號數目 才可以加右括號 3 結束 左右括號均用完 思路...

演算法 回溯法(雙路徑回溯 括號生成)

leetcode 22 括號生成 我想到了兩種思路來解決這個問題 1 先添左括號,沒有左括號,再新增右括號,知道左右括號數量相加等於2倍的n 然後撤銷並重新新增,直到找出所有組合,如圖所示 其實就是能進則進,進不了則換,換不了則退 回溯法。找返回條件 左括號個數 右括號個數 n或者左括號個數 右括號...