22. 括号生成

135 阅读4分钟

【题目】

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

  示例 1:

输入: n = 3
输出: ["((()))","(()())","(())()","()(())","()()()"]

示例 2:

输入: n = 1
输出: ["()"]

  提示:

  • 1 <= n <= 8

【题目解析】

思路:

生成有效括号的问题可以通过回溯算法来解决。回溯算法是一种通过试错来寻找所有解的算法,如果当前选择不正确,就回溯至上一步或几步,进行其他可能的选择。这个过程可以形象地描述为在决策树上的深度优先搜索。

具体到括号生成问题,我们可以定义两个变量:left 记录已使用的左括号数量,right 记录已使用的右括号数量。从空字符串开始,我们可以添加一个左括号,如果它不会导致左括号数量超过 n;我们也可以添加一个右括号,如果它不会使得右括号数量超过左括号数量。这样的递归生成所有可能的组合,并在递归过程中剪枝,只留下有效的括号组合。

class Solution:
    def generateParenthesis(self, n: int) -> List[str]:
        def backtrack(S='', left=0, right=0):
            # 如果字符串S的长度等于2倍的n,意味着左右括号数量相等,是有效的组合
            if len(S) == 2 * n:
                ans.append(S)
                return
            # 如果左括号数量小于n,可以添加一个左括号
            if left < n:
                backtrack(S+'(', left+1, right)
            # 如果右括号数量小于左括号,可以添加一个右括号
            if right < left:
                backtrack(S+')', left, right+1)

        ans = []
        backtrack()
        return ans

执行:

image.png

【总结】

适用问题类型: 回溯算法适用于需要枚举各种可能性的问题,特别是在解决需要考虑所有可能组合的决策问题时效果显著。这些问题通常有以下特点:

  • 有明确的目标条件或结束状态。
  • 需要从多个选项中选择一个,并且每次选择后选项会发生变化。
  • 存在有效和无效的选择路径,且需要剪枝以避免无效搜索。

括号生成问题就是一个典型的回溯算法适用场景,因为它要求生成所有有效的括号匹配组合,这些组合的总数事先未知,并且每增加一个括号都会改变后续的选择。

使用的算法: 括号生成问题使用了回溯算法。回溯算法通常通过递归来实现,它允许我们进行试错,如果当前路径不正确,我们可以退回到之前的步骤,选择不同的路径继续尝试。在括号生成的具体实现中,这意味着我们递归地添加左括号和右括号,并在每一步中确保:

  • 不会添加超过给定对数 n 的左括号。
  • 不会添加超过已经添加的左括号数量的右括号。

每当我们添加一个括号时,我们都会检查是否还可以添加更多的括号或是否达到了有效组合的条件。这个过程会生成一个决策树,其中包含所有可能的括号组合。回溯算法会遍历这棵决策树,并剪枝,以确保只有有效的括号组合被包括在内。

算法性能: 回溯算法的时间复杂度通常较难直接计算,因为它依赖于解空间的大小,以及剪枝策略的效率。对于括号生成问题,算法的时间复杂度为 O(4ⁿ/√n​),这来自于卡特兰数的界定,它近似地描述了结果的数量。空间复杂度为 O(n),因为我们的递归深度最大为 2n,并且我们需要存储每个有效组合。

总结与扩展: 括号生成问题不仅提供了回溯算法的一个经典案例,而且还强调了递归在构建解决方案时的重要性。通过这个问题,我们学会了如何设计算法来系统地探索所有可能的解决方案,并及时地避免无效的搜索。这种方法不仅适用于生成括号,还可扩展到其他需要系统生成组合的问题,例如排列组合、解数独、以及其他许多需要枚举的问题。掌握回溯算法对于一个软件工程师来说至关重要,它是许多算法问题解决方案的基石。

题目链接

括号生成