题目描述:
数字
n代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的括号组合。示例 1:
输入:n = 3 输出:["((()))","(()())","(())()","()(())","()()()"]示例 2:
输入:n = 1 输出:["()"]
解题思路:
方法一:回溯 + 剪枝
有效括号的条件:
-
左括号数量 ≤ n
-
右括号数量 ≤ 左括号数量 (否则会出现
)(这种无效组合) -
左括号数量 == 右括号数量 == n 时,得到一个有效组合
回溯三步:
-
选择:放左括号或右括号
-
递归:继续生成下一个括号
-
撤销:删除当前括号
具体过程示例:
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) |