返回

LeetCode Hot 100: 022 括号生成 - 大力出奇迹

IOS

022.括号生成:深入剖析回溯算法与优化技巧

深入浅出,剖析解题思路

踏上 LeetCode Hot 100 的征程,我们来到了一道看似简单却颇具挑战的题目:022. 括号生成。乍一看,这道题要求我们生成所有有效的括号序列,似乎不难。但深入探究后,你会发现它蕴含着不小的组合技巧。

  1. 括号生成问题的本质是一个组合问题,我们需要生成所有满足左右括号匹配且嵌套正确的序列。解决此类问题的一个利器便是回溯算法。

回溯算法是一种深度优先搜索算法,它从一个初始状态出发,逐步尝试所有可能的路径,并在不可行时回溯到上一步,尝试不同的选择。对于括号生成问题,我们以一个空串作为初始状态,逐步添加左括号或右括号,直到生成一个有效的序列。

代码实现,直观明了

def generateParenthesis(n):
    def backtrack(s, left, right):
        if len(s) == 2 * n:
            res.append(s)
            return
        if left < n:
            backtrack(s + '(', left + 1, right)
        if right < left:
            backtrack(s + ')', left, right + 1)
    res = []
    backtrack('', 0, 0)
    return res

在这个实现中,我们用一个空串 s 代表当前生成的序列,两个变量 leftright 分别记录左括号和右括号的数量,res 则用于存储最终结果。

回溯函数 backtrack 的工作方式如下:首先检查当前序列是否完整(即长度达到 2 * n),如果是则将其添加到结果中。否则,根据以下规则探索两种选择:

  • 如果左括号数量小于 n,则添加一个左括号;
  • 如果右括号数量小于左括号数量,则添加一个右括号。

优化技巧,提升效率

虽然回溯算法可以解决问题,但效率并不高,尤其是对于较大的 n 值。因此,我们需要引入一些优化技巧:

  • 剪枝: 在回溯过程中,如果当前序列已经不满足有效性条件(如左括号数量大于右括号数量),则直接返回,避免进一步的无用探索。
  • 动态规划: 我们可以使用动态规划来存储已经计算过的子问题的结果。当需要解决相同子问题时,直接返回存储的结果,避免重复计算。

复杂度分析,深入理解

  • 时间复杂度: O(4^n / sqrt(n))
  • 空间复杂度: O(4^n / sqrt(n))

总结要点,全面回顾

    1. 括号生成是一道经典的组合问题,可以使用回溯算法解决;
  • 通过剪枝和动态规划等优化技巧,我们可以提高回溯算法的效率;
  • 理解这道题的解题思路和技巧,有助于我们解决其他类似的组合问题。

常见问题解答,释疑解惑

1. 回溯算法和递归有什么区别?

回溯算法是一种深度优先搜索算法,而递归是一种函数调用自身的编程技巧。回溯算法会在需要时返回上一步,而递归则会一直深入调用自身,直到达到基线条件。

2. 剪枝如何提高效率?

剪枝可以避免探索无效的序列,从而减少回溯算法的搜索范围,显著提高效率。

3. 动态规划如何应用于括号生成问题?

我们可以用一个二维数组 dp 存储已经计算过的子问题的答案。其中 dp[i][j] 表示使用 i 个左括号和 j 个右括号生成有效序列的方案数。通过动态规划,我们可以避免重复计算相同子问题。

4. 为什么复杂度是 O(4^n / sqrt(n))?

这是一个经典的卡特兰数问题。生成长度为 2n 的括号序列共有 4 种基本操作:左括号、右括号、匹配的左右括号对以及无效序列。对于每个有效的序列,会有 n 个匹配的左右括号对,因此有 C(n) = 4^n / (n + 1) 个有效的序列。经过化简,得到复杂度为 O(4^n / sqrt(n))。

5. 除了回溯算法,还有其他解决括号生成问题的方法吗?

除了回溯算法,我们还可以使用数学公式、Catalan 数等数学工具来解决括号生成问题,但回溯算法是最直观和易于理解的方法。