动态规划(二):括号生成
题目:
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例 1:
输入:n = 3
输出:[“((()))”,“(()())”,“(())()”,“()(())”,“()()()”]
示例 2:
输入:n = 1
输出:[“()”]
提示:
1 <= n <= 8
解:
from typing import List
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
def gen(p, lc, rc, r, n):
if lc > n:
return
if lc == n and rc == n:
r.append(‘’.join§)
p.append(‘(’)
print(“p,lc,rc,r当前状态为”,p,lc,rc,r)
lc += 1
gen(p, lc, rc, r, n)
p.pop()
print(“p,lc,rc,r当前状态为”, p, lc, rc, r)
lc -= 1
if lc > rc:
p.append(‘)’)
print(“p,lc,rc,r当前状态为”, p, lc, rc, r)
rc += 1
gen(p, lc, rc, r, n)
p.pop()
print(“p,lc,rc,r当前状态为”, p, lc, rc, r)
rc -= 1
results = []
gen([], 0, 0, results, n)
return results
%%
s = Solution()
print(s.generateParenthesis(n = 2))
代码讲解
代码逐行解释如下:
-
from typing import List
: 这一行导入了
typing
模块中的
List
类型。它用于为函数的参数和返回值提供类型提示。在这里,它表明
generateParenthesis
方法将返回一个字符串列表。 -
class Solution:
: 这定义了一个名为
Solution
的类,包含了
generateParenthesis
方法。 -
def generateParenthesis(self, n: int) -> List[str]:
: 这是
Solution
类中
generateParenthesis
方法的定义。它接受一个整数
n
作为输入,并返回一个字符串列表(
List[str]
)。该方法将用于生成包含
n
对括号的所有合法组合。 -
def gen(p, lc, rc, r, n):
: 这是一个名为
gen
的辅助函数,用于递归地生成括号的组合。它接受以下参数:-
p
: 一个列表,表示当前正在构建的括号组合。 -
lc
: 一个整数,表示当前组合中左括号
'('
的个数。 -
rc
: 一个整数,表示当前组合中右括号
')'
的个数。 -
r
: 存储结果(合法括号组合)的列表。 -
n
: 一个整数,表示要生成的括号对数。
-
-
if lc > n: return
: 这是递归函数的基本情况。如果左括号的个数
lc
超过了
n
,则函数直接返回,因为它不是一个合法的组合。 -
if lc == n and rc == n: r.append(''.join(p))
: 这检查左括号的个数
lc
和右括号的个数
rc
是否都等于
n
。如果是,意味着我们已经形成了一个合法的括号组合,并将其添加到结果列表
r
中。 -
p.append('(')
: 向当前组合
p
中添加一个左括号
'('
。 -
lc += 1
: 增加左括号的个数
lc
。 -
gen(p, lc, rc, r, n)
: 递归调用
gen
函数,传入更新后的
p
、
lc
、
rc
、
r
和
n
的值。这将探索一个额外的左括号的组合。 -
p.pop()
: 从当前组合
p
中删除最后一个元素(即左括号
'('
)。 -
lc -= 1
: 减少左括号的个数
lc
。 -
if lc > rc:
: 检查左括号的个数
lc
是否大于右括号的个数
rc
。如果是,表示我们可以向当前组合添加一个右括号,形成一个合法的括号组合。 -
p.append(')')
: 向当前组合
p
中添加一个右括号
')'
。 -
rc += 1
: 增加右括号的个数
rc
。 -
gen(p, lc, rc, r, n)
: 递归调用
gen
函数,传入更新后的
p
、
lc
、
rc
、
r
和
n
的值。这将探索一个额外的右括号的组合。 -
p
: 最后的
p
列表是函数返回时的状态,它表示了一个合法的括号组合。所有生成的括号组合将存储在结果列表
r
中,最终函数返回
r
。
动态演示
算法思路
在这个问题中,虽然代码并没有显式使用动态规划的方法,但实际上,它使用了类似动态规划的思想来解决问题。
在动态规划中,通常会将问题分解为子问题,并通过求解子问题来得到原问题的解。这种思想在递归中得到体现。在代码中,
gen
函数就是一个递归函数,它通过不断调用自身来探索所有可能的括号组合。
具体体现动态规划思想的部分是在
gen
函数的递归过程中:
-
gen(p, lc, rc, r, n)
递归地构建括号组合,对于每个当前的状态
(p, lc, rc)
,它根据条件进行判断和分支。 -
当满足
lc == n
和
rc == n
的条件时,将当前的括号组合加入到结果列表
r
中。这表示已经找到了一个合法的括号组合。 -
在递归过程中,每次递归都会构建一个新的
p
列表,并在其中添加左括号或右括号。通过这样的方式,
gen
函数会尝试所有可能的括号组合,直到达到合法的括号组合。
虽然这个问题没有显式地使用动态规划的数组来存储子问题的解,但是通过递归过程,它仍然实现了类似动态规划的思想,将大问题拆分成更小的子问题,并通过递归的方式逐步解决子问题,最终得到原问题的解。
需要注意的是,虽然这种递归实现在小规模问题上是有效的,但对于较大的
n
值,由于存在大量的重复计算,它的时间复杂度可能较高。在实际应用中,可以进一步优化使用动态规划的方法来存储子问题的解,避免重复计算,提高效率。
做完这道题,让我觉得不能用正常的线性数据结构思维的方法来进行迭代,也就是遍历不仅限于从0到N,从0到N的遍历每个元素后只跟着一个值,一种可能性。而对于该题就适用二叉树的遍历方法,要生成所有可能性,每一个位置上要么“(”要么“)”,正好两种可能性。然后使用了lc和rc来控制位置。比如在该题目中,把它想象成一棵树,就是每次向左子树上探索,达到约束条件后退回,探索右子树。而在二叉树的遍历中,递归和回溯法是非常适用的,