LeetCode 22. 括号生成(中)
括号生成和判定的基本问题多基于回溯法解决
题目
给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。
示例
例如,给出 n = 3,生成结果为:
1 |
|
考察知识点
字符串、回溯算法、动态规划
核心思想
方法一:暴力法
思路
我们可以生成所有 \(2^{2n}\) 个 '(' 和 ')' 字符构成的序列(一共有n对括号,字符串长度为2n,每一位上可能出现的选择为2个,要么是’(',要么是‘)’,所以有\(2^{2n}\)个可能的序列)。然后,我们将检查每一个是否有效。
算法
为了生成所有序列,我们使用递归。长度为 n
的序列就是 '(' 加上所有长度为 n-1
的序列,以及 ')' 加上所有长度为 n-1
的序列。
为了检查序列是否为有效的,我们会跟踪平衡,也就是左括号的数量减去右括号的数量的净值。如果这个值始终小于零或者不以零结束,该序列就是无效的,否则它是有效的。
方法二:回溯法
思路和算法
只有在我们知道序列仍然保持有效时才添加 '('
or ')'
,而不是像 方法一 那样每次添加。我们可以通过跟踪到目前为止放置的左括号和右括号的数目来做到这一点,
如果我们还剩一个位置,我们可以开始放一个左括号。 如果它不超过左括号的数量,我们可以放一个右括号。
有1组括号的组合方式,即"()",
方法三:动态规划(官方题解的闭合数方法)
有2组括号时,新的组合方式为 "(())"或者"()()",
有3组括号时,新的组合方式为 "((()))", "(()())", "(())()", "()(())", "()()()"
可以发现规律,我们考虑由i
组括号组成的多个括号排列中的一个。考虑这个括号排列中最左边的括号,它一定是一个左括号,那么它可以和它对应的右括号组成一组完整的括号 "( )",我们假设这一组括号是相比由 i-1
组括号组成的括号排列,而增加进来的新括号。
这个新加进来的括号,剩下的旧括号要么在这一组新增的括号内部"(())"->"((()))"
,要么在这一组新增括号的外部(右侧)(())->(())()
。
依赖i
和i+1
的关系,从i=0
一直推导到i=n
,即可得到n组括号的所有不重复组合方式。
既然知道了 i<n
的情况,那我们就可以对所有情况进行遍历:
"(" + 【i=p时所有括号的排列组合】 + ")" + 【i=q时所有括号的排列组合】
其中 p + q = n - 1
,且 p q
均为非负整数。
事实上,当上述 p
从 0
取到 n-1
,q
同时也从 n-1
取到 0
,所有情况就遍历完了。
关于 p q
的解释,当 i=3
(即求解有3组括号时,新的组合方式)时,要遍历有0组括号、有1组括号、有2组括号三种情况,由于p + q = i -1 = 2
,且 p
从 0
变到 i-1
,q
同时也从 i-1
变到 0
,所以有三种情况,分别是p=0,q=2/p=1,q=1/p=2,q=0
,这三种情况下,已经有2组括号,再加上一组,就得到了有3组括号时,新的组合方式。
注:上述遍历是没有重复情况出现的,即当 (p1,q1)≠(p2,q2)
时,按上述方式取的括号组合一定不同。
Python版本
方法一暴力枚举法的实现
1 |
|
复杂度分析
时间复杂度:\(O(2^{2n}n)\),对于 \(2^{2n}\)个序列中的每一个,我们用于建立和验证该序列的复杂度为 \(O(n)\)。
空间复杂度:\(O(2^{2n}n)\),简单地,每个序列都视作是有效的。请参见方法三以获得更严格的渐近界限。
方法二回溯法的实现
1 |
|
我们的复杂度分析依赖于理解 generateParenthesis(n) 中有多少个元素。这个分析超出了本文的范畴,但事实证明这是第 n 个卡塔兰数 \(\dfrac{1}{n+1}\binom{2n}{n}\),这是由 \(\dfrac{4^n}{n\sqrt{n}}\) 渐近界定的。 时间复杂度:\(O(\dfrac{4^n}{\sqrt{n}})\),在回溯过程中,每个有效序列最多需要 n 步。 空间复杂度:\(O(\dfrac{4^n}{\sqrt{n}})\),如上所述,并使用 \(O(n)\)O(n) 的空间来存储序列。
动态规划方法实现
1 |
|
另一个简洁的写法
1 |
|
时间和空间复杂度:\(O(\dfrac{4^n}{\sqrt{n}})\),该分析与 方法二 类似。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!