返回

括号生成:算法思想、多种解法剖析,助力高效刷题

Android

破解括号生成算法:动态规划、递归、栈和回溯法大 PK

对于编程员来说,括号生成算法早已不是一个陌生的概念,它是计算机科学中一道经典的动态规划问题,目标是生成所有合法的括号序列,即所有左括号都必须与一个右括号配对,且每个右括号都必须与一个左括号配对。

虽然这个看似简单的任务,背后却隐藏着精妙的算法设计。本文将深入剖析括号生成算法,探讨四种广为人知的算法:动态规划、递归、栈和回溯法。我们将逐一解析它们的思路、优缺点,并通过代码示例来加深理解。

算法大 PK

动态规划:全面但低效

动态规划是一种自底向上的解决问题的方法,将问题划分为多个子问题,然后逐个解决子问题,并存储子问题的解以备后续使用。对于括号生成问题,我们可以将子问题定义为生成长度为 n 的合法括号序列。

def generate_parenthesis_dp(n):
    """
    生成所有长度为 n 的合法括号序列。

    Args:
        n: 括号序列的长度。

    Returns:
        所有长度为 n 的合法括号序列的列表。
    """
    dp = [[] for _ in range(n + 1)]
    dp[0] = [""]

    for i in range(1, n + 1):
        for j in range(i):
            for left in dp[j]:
                for right in dp[i - j - 1]:
                    dp[i].append(left + "(" + right + ")")

    return dp[n]

动态规划算法的好处在于全面,可以生成所有合法的括号序列。然而,它的缺点也很明显,就是时间复杂度和空间复杂度都较高,分别为 O(n^2 * 4^n) 和 O(n^2 * 4^n)。

递归:简洁但容易陷入死循环

递归是一种自顶向下的解决问题的方法,将问题分解为更小的子问题,然后递归地解决子问题。对于括号生成问题,我们可以将子问题定义为生成长度为 n 的合法括号序列。

def generate_parenthesis_recursive(n):
    """
    生成所有长度为 n 的合法括号序列。

    Args:
        n: 括号序列的长度。

    Returns:
        所有长度为 n 的合法括号序列的列表。
    """
    if n == 0:
        return [""]

    result = []
    for i in range(n):
        for left in generate_parenthesis_recursive(i):
            for right in generate_parenthesis_recursive(n - i - 1):
                result.append(left + "(" + right + ")")

    return result

递归算法的优点是简洁明了,思路清晰。然而,它的缺点在于容易陷入死循环,如果处理不好,可能会导致程序崩溃。

栈:高效但限定性强

栈是一种后进先出的数据结构,可以用来存储括号。我们可以将左括号压入栈中,当遇到右括号时,将栈顶的左括号弹出,并将其与右括号配对。

def generate_parenthesis_stack(n):
    """
    生成所有长度为 n 的合法括号序列。

    Args:
        n: 括号序列的长度。

    Returns:
        所有长度为 n 的合法括号序列的列表。
    """
    result = []
    stack = []

    def generate(left, right):
        if left == n and right == n:
            result.append("".join(stack))
            return

        if left < n:
            stack.append("(")
            generate(left + 1, right)
            stack.pop()

        if right < n and left > right:
            stack.append(")")
            generate(left, right + 1)
            stack.pop()

    generate(0, 0)

    return result

栈算法的优点在于高效,时间复杂度和空间复杂度都是 O(4^n)。然而,它的缺点在于限定性强,只能生成特定的括号序列,无法生成所有合法的括号序列。

回溯法:灵活但效率较低

回溯法是一种穷举搜索算法,可以用来解决各种组合优化问题。对于括号生成问题,我们可以将子问题定义为生成长度为 n 的合法括号序列。我们可以从一个空序列开始,并逐个添加左括号和右括号,直到序列的长度达到 n。在添加括号时,我们需要检查序列是否合法。如果序列合法,我们就继续添加括号。如果序列不合法,我们就回溯到上一步,并尝试添加另一个括号。

def generate_parenthesis_backtracking(n):
    """
    生成所有长度为 n 的合法括号序列。

    Args:
        n: 括号序列的长度。

    Returns:
        所有长度为 n 的合法括号序列的列表。
    """
    result = []

    def backtrack(s, left, right):
        if len(s) == 2 * n:
            result.append(s)
            return

        if left < n:
            backtrack(s + "(", left + 1, right)

        if right < left:
            backtrack(s + ")", left, right + 1)

    backtrack("", 0, 0)

    return result

回溯法的优点在于灵活,可以生成所有合法的括号序列。然而,它的缺点在于效率较低,时间复杂度和空间复杂度都是 O(4^n)。

性能比较

下表比较了动态规划、递归、栈和回溯法在括号生成问题上的性能:

算法 时间复杂度 空间复杂度 优点 缺点
动态规划 O(n^2 * 4^n) O(n^2 * 4^n) 全面 效率低
递归 O(4^n) O(n) 简洁 容易死循环
O(4^n) O(n) 高效 限定性强
回溯法 O(4^n) O(n) 灵活 效率低

总结

括号生成算法是计算机科学中一道经典问题,有多种解法。其中,动态规划算法全面但低效,递归算法简洁但容易陷入死循环,栈算法高效但限定性强,回溯法灵活但效率较低。在实际应用中,我们可以根据具体情况选择合适的算法来解决括号生成问题。

常见问题解答

1. 什么是括号生成问题?

括号生成问题是指生成所有合法的括号序列,即所有左括号都必须与一个右括号配对,且每个右括号都必须与一个左括号配对。

2. 如何使用动态规划解决括号生成问题?

动态规划是一种自底向上的解决问题的方法,将问题划分为多个子问题,然后逐个解决子问题,并存储子问题的解以备后续使用。对于括号生成问题,我们可以将子问题定义为生成长度为 n 的合法括号序列。然后,我们可以使用动态规划算法逐个生成长度为 1、2、3、...、n 的合法括号序列。

3. 如何使用递归解决括号生成问题?

递归是一种自顶向下的解决问题的方法,将问题分解为更小的子问题,然后递归地解决子问题。对于括号生成问题,我们可以将子问题定义为生成长度为 n 的合法括号序列。然后,我们可以使用递归算法递归地生成长度为 1、2、3、...、n 的合法括号序列。

4. 如何使用栈解决括号生成问题?

栈是一种后进先出的数据结构,可以用来存储括号。我们可以将左括号压入栈中,当遇到右括号时,将栈顶的左括号弹出,并将其与右括号配对。然后,我们就可以得到一个合法的括号序列。

5. 如何使用回溯法解决括号生成问题?

回溯法是一种穷举搜索算法,可以用来解决各种组合优化问题。对于括号生成问题,我们可以将子问题定义为生成长度为 n 的合法括号序列。然后,我们可以从一个空序列开始,并逐个添加左括号和右括号,直到序列的长度达到 n。在添加括号时,我们需要检查序列是否合法。如果序列合法,我们就继续添加括号。如果序列不合法,我们就回溯到上一步,并尝试添加另一个括号。