【回溯-2】22.括号生成

题目描述:

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

示例 1:

复制代码
输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]

示例 2:

复制代码
输入:n = 1
输出:["()"]

解题思路:

方法一:回溯 + 剪枝

有效括号的条件:

  1. 左括号数量 ≤ n

  2. 右括号数量 ≤ 左括号数量 (否则会出现 )( 这种无效组合)

  3. 左括号数量 == 右括号数量 == n 时,得到一个有效组合

回溯三步:

  1. 选择:放左括号或右括号

  2. 递归:继续生成下一个括号

  3. 撤销:删除当前括号

具体过程示例:

n = 2

复制代码
              ""
            /    \
          (       (不合法,右括号多于左括号)
        /   \
      ((     ()
     /  \      \
   (()  ()(    ())  ← ()) 不合法,右括号多于左括号
   /      \
 (())    ()()

有效组合 :(())、()()

代码实现:

cpp 复制代码
class Solution {
public:
    vector<string> generateParenthesis(int n) {
        vector<string> result;
        string path;
        backtrack(n, 0, 0, path, result);
        return result;
    }
    
private:
    void backtrack(int n, int left, int right, string& path, vector<string>& result) {
        // 终止条件:左右括号都用完了
        if (left == n && right == n) {
            result.push_back(path);
            return;
        }
        
        // 选择左括号:只要左括号没用完
        if (left < n) {
            path.push_back('(');
            backtrack(n, left + 1, right, path, result);
            path.pop_back();  // 撤销
        }
        
        // 选择右括号:只要右括号数量 < 左括号数量
        if (right < left) {
            path.push_back(')');
            backtrack(n, left, right + 1, path, result);
            path.pop_back();  // 撤销
        }
    }
};

复杂度分析:

维度 复杂度 说明
时间复杂度 O(4^n / √n) 第 n 个卡特兰数,约等于 4^n / (n√n)
空间复杂度 O(n) 递归栈深度 + path 长度

卡特兰数 :C(2n, n) / (n + 1),是有效括号组合的数量。

关键细节:

1. 为什么 right < left 才能放右括号?

因为有效括号要求任意前缀中左括号数量 ≥ 右括号数量。

如果 right >= left,放右括号会导致 ) 多于 (,形成无效组合。

2. 为什么 left < n 才能放左括号?

因为总共只有 n 对括号,左括号最多放 n 个。

3. 为什么不用判断 right < n?

因为 right < left 已经隐含了 right < n(因为 left ≤ n)。

4. 终止条件为什么是 left == n && right == n?

当左右括号都用完时,path 就是一个完整的有效组合。

方法二:暴力枚举

生成所有 2^(2n) 个括号组合,然后验证每个是否有效。

代码实现:

cpp 复制代码
class Solution {
public:
    vector<string> generateParenthesis(int n) {
        vector<string> result;
        generateAll(n, 0, "", result);
        return result;
    }
    
private:
    void generateAll(int n, int pos, string current, vector<string>& result) {
        if (pos == 2 * n) {
            if (isValid(current)) result.push_back(current);
            return;
        }
        generateAll(n, pos + 1, current + "(", result);
        generateAll(n, pos + 1, current + ")", result);
    }
    
    bool isValid(string& s) {
        int balance = 0;
        for (char c : s) {
            if (c == '(') balance++;
            else balance--;
            if (balance < 0) return false;
        }
        return balance == 0;
    }
};

复杂度:时间 O(2^(2n) × n),空间 O(n)

缺点:枚举了大量无效组合,效率低。

方法三:动态规划

思路 :n 对括号的组合 = ( + i 对括号 + ) + n-1-i 对括号

代码实现:

cpp 复制代码
class Solution {
public:
    vector<string> generateParenthesis(int n) {
        vector<vector<string>> dp(n + 1);
        dp[0] = {""};
        
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j < i; j++) {
                for (string& left : dp[j]) {
                    for (string& right : dp[i - 1 - j]) {
                        dp[i].push_back("(" + left + ")" + right);
                    }
                }
            }
        }
        
        return dp[n];
    }
};

复杂度:时间 O(4^n / √n),空间 O(4^n / √n)

三种方法对比:

方法 时间复杂度 空间复杂度 推荐度
回溯 + 剪枝 O(4^n / √n) O(n) ⭐⭐⭐⭐⭐
暴力枚举 O(2^(2n) × n) O(n) ⭐⭐
动态规划 O(4^n / √n) O(4^n / √n) ⭐⭐⭐

总结:

要点 说明
核心思想 回溯:放左括号或右括号,剪枝保证有效
关键条件 left < n 放左括号,right < left 放右括号
时间复杂度 O(4^n / √n)
空间复杂度 O(n)
相关推荐
longlongzihan1 小时前
LeetCode 1004.最大连续1的个数III 滑动窗口及动态规划题解
c++·算法·leetcode·dp·滑动窗口
朝朝辞暮i1 小时前
C++ 第 22 课:const —— 不允许随便修改的数据
开发语言·c++·算法
蒸蒸yyyyzwd2 小时前
力扣二分总结-lowerbound
c++
Chen—LSN2 小时前
数据结构——拿捏复杂度
java·c语言·开发语言·数据结构·经验分享·笔记·算法
Chen_harmony2 小时前
六、排序详解
c语言·数据结构·算法·排序算法
ShineWinsu2 小时前
对于Redis:List类型的解析
数据库·c++·redis·链表·缓存·面试·list
-dzk-2 小时前
【贪心算法】LC 55.跳跃游戏
算法·游戏·贪心算法
ofoxcoding2 小时前
Jev 模型评测深度解析:核心得分、能力局限与最佳适用任务场景
人工智能·算法·机器学习·ai
Evand J2 小时前
【MATLAB例程】三维A*路径规划与TOA-AOA-TDOA融合定位算法。附下载链接
数据库·算法·matlab