LeetCode22 括号生成(两种解法比较)

59 阅读3分钟

leetcode.cn/problems/ge…

image.png

解法一:回溯法

题意可以转化为,现在有 2n 个位置,每个位置可以放置 ( 或者 ),所有括号组合中,有多少个是合法的?

生成括号组合的思路其实就和全排列一致,暴力穷举就行了

判断有效括号的题解可以参考这篇文章,主要是借助栈

func generateParenthesis(n int) []string {
    var res []string
    choiceList := []string{"(", ")"}
    backtrack(choiceList, n, "", &res)
    return res
}

func backtrack(choiceList []string, n int, combination string, res *[]string){
    if len(combination) == 2*n{
        if isValid(combination){ // 合法的括号才放入结果集
            *res = append(*res, combination)
        }
        return
    }
    for _, choice := range choiceList{
        combination+=choice // 选择一个括号加入子集
        backtrack(choiceList, n, combination, res)
        combination = combination[:len(combination)-1] // 撤销选择
    }
}

func isValid(str string) bool{
    var leftStack []rune // 栈空间存放左括号
    for _, c := range str {
        if c == '('{ // 左括号压栈
            leftStack = append(leftStack, c)
        } else { // 右括号,寻找是否有匹配的左括号
            if len(leftStack) > 0 {
                leftStack = leftStack[:len(leftStack)-1] // 弹出栈顶左括号
            } else {
                return false // 无左括号可匹配
            }
        }
    }
    // 是否所有的左括号都被匹配了
    return len(leftStack) == 0
}
时间复杂度分析
  • 每个位置有 2 种选择,总共有 2*n 个位置,因此生成所有可能组合的时间复杂度为 O(2^2n)。
  • 对于每个生成的组合,使用栈验证其有效性的时间复杂度为 O(2n),因为需要遍历组合中的每个字符

由于需要对每个生成的组合逐一进行验证,所以总体时间复杂度为 O(2^2n * 2n) = O(n * 4^n)

image.png

优化

为了减少不必要的穷举,我们可以优化一下剪枝策略,在回溯过程中只递归生成有效括号,而不是先暴力穷举所有括号组合才去判断是否合法

分析合法括号串有以下性质:

  1. 左括号数量一定等于右括号数量
  2. 对于一个「合法」的括号字符串组合 p,必然对任何 0 <= i < len(p) 都有:子串 p[0...i] 中左括号的数量都大于或等于右括号的数量。因为从左往右算的话,肯定是左括号多嘛,到最后左右括号数量相等,才说明这个括号组合是合法的。

我们可以用 left 记录还可以使用多少个左括号,用 right 记录还可以使用多少个右括号

func generateParenthesis(n int) []string {
    var res []string
    if n == 0{
        return res
    }
    backtrack(n, n, "", &res) // 可用的左括号和右括号数量初始为 n
    return res
}

func backtrack(left, right int, path string, res *[]string){
    if left > right{ // 若左括号剩下的多,说明不合法
        return
    }
    if left < 0 || right < 0{ // 若左/右括号剩下数量小于0,说明不合法
        return
    }
    if left == 0 && right == 0{ // 当所有括号都恰好用完时,得到一个合法的括号组合
        *res = append(*res, path)
        return
    }
    // 尝试放一个左括号
    path += "("
    backtrack(left-1, right, path, res)
    path = path[:len(path)-1] // 撤消选择
    // 尝试放一个右括号
    path += ")"
    backtrack(left, right-1, path, res)
    path = path[:len(path)-1] // 撤消选择
}
时间复杂度分析

在回溯过程中根据这两个变量的大小关系进行剪枝,避免生成无效组合。

image.png