合法的括号匹配的问题之前已经讲解过了,现在再看一个括号生成的题目。
题目描述
给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。
例如,给出 n = 3,生成结果为:
[ “((()))”, “(()())”, “(())()”, “()(())”, “()()()” ]
题目分析
既然之前已经分析过合法的括号判断,那么只要把n个左括号和n个右括号全排列,然后对排列的结果去做一个有效验证,好了,这个问题解决了。但是仔细一分析,判断一个括号是否有效,复杂度 ,生成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项,我们大致可以根据卡特兰数的性质,分析问题的规模。我们知道卡特兰数的渐进上界是
,而每一个合法的序列,我们是通过
次添加字符生成出来的。
动态规划法
既然这个问题和卡特兰数相关,那么一定可以根据卡特兰数的特点,一定可以把这个问题转换成以前所有状态的穷举。说的通俗一点就是,如果我们研究问题规模为n,那么问题规模之和为n-1的两两组合一定可以用得上,如果这句话理解不了可以直接跳到下一段。
我们为了降低问题规模,可以考虑在问题规模为n-1的情况上加一对括号,可是这个多出来的一对括号应该往哪加。显然,我们可以把这个括号加在所有的合法匹配的括号之后。如果合法匹配数为0的时候,我们就把括号加在了最前面,就是
,再者我们可以把括号加到一对合法括号后面,也是
依次类推,n-1个合法的括号总共可以分解出n-1种情况。每个情况分为两部分,前面一部分是dp_i,后面一部分是dp_n-1-i,其实也就是两部分满足下标之和为n-1就行。
据此我们可以写出动态规划的递归式。
。事实上,几乎所有的卡特兰数问题都可以写成类似的递推式,对所有两两组合为n-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,但是这个代码的时间复杂度不是 ,相比于上面的回溯法,其实时间复杂度的指数部分还是保持的,单是求问题规模等于n的情况,时间复杂度就已经达到了 。