文章目录
1. 问题描述
括号生成问题要求生成所有由 n
对有效括号组成的字符串组合。例如,当 n = 3
时,可能的组合包括 "((()))"
, "(()())"
, "()(())"
, "()()()"
等。有效括号需满足以下条件:
- 每个左括号必须有对应的右括号。
- 括号顺序必须正确(例如
")("
是无效的)。
2. 回溯算法思想
回溯算法是一种通过递归遍历所有可能解并剪枝无效路径的算法。其核心步骤如下:
- 选择:在每一步尝试添加一个左括号或右括号。
- 约束:通过条件判断确保括号有效性。
- 撤销:当发现当前路径无法生成有效解时,回退到上一步。
3. 解决方案设计
核心思路
- 左括号优先:先尽可能添加左括号(最多
n
个)。 - 右括号限制:右括号数量不能超过左括号,避免出现无效组合(如
")("
)。
递归条件
- 终止条件:当前字符串长度为
2 * n
时,保存结果。 - 递归路径:
- 若左括号数
< n
,添加左括号。 - 若右括号数
< 左括号数
,添加右括号。
- 若左括号数
4. 完整代码实现
import java.util.ArrayList;
import java.util.List;
public class GenerateParentheses {
public List<String> generateParenthesis(int n) {
List<String> result = new ArrayList<>();
backtrack(result, new StringBuilder(), 0, 0, n);
return result;
}
private void backtrack(List<String> result, StringBuilder current,
int left, int right, int n) {
// 终止条件:当前字符串长度为2n
if (current.length() == 2 * n) {
result.add(current.toString());
return;
}
// 添加左括号的条件:left < n
if (left < n) {
current.append("(");
backtrack(result, current, left + 1, right, n);
current.deleteCharAt(current.length() - 1); // 回溯
}
// 添加右括号的条件:right < left
if (right < left) {
current.append(")");
backtrack(result, current, left, right + 1, n);
current.deleteCharAt(current.length() - 1); // 回溯
}
}
}
5. 算法分步解析
关键代码说明
- 初始化:
generateParenthesis
方法初始化结果列表并启动回溯。 - 回溯函数参数:
result
:保存所有有效组合。current
:当前生成的字符串。left
/right
:已使用的左/右括号数量。
- 终止条件:当字符串长度达到
2n
时,保存结果。 - 递归添加括号:
- 左括号:优先添加,直到数量达到
n
。 - 右括号:仅在数量小于左括号时添加,确保有效性。
- 左括号:优先添加,直到数量达到
- 回溯操作:递归返回时删除最后添加的括号,尝试其他路径。
递归树示意图(以 n=2 为例)
开始("")
├─ 添加 "(" → "(("
│ ├─ 添加 "(" → "(()"
│ │ └─ 添加 ")" → "(())" ✔️
│ └─ 添加 ")" → "()"
│ ├─ 添加 "(" → "()("
│ │ └─ 添加 ")" → "()()" ✔️
├─ 添加 ")" → 无效,跳过
6. 复杂度分析
时间复杂度
- 卡特兰数模型:有效括号组合数为第
n
个卡特兰数,约为C(n) = (1/(n+1)) * C(2n, n)
。 - 时间复杂度:O(4^n / √n),每个递归步骤有两次选择(左或右)。
空间复杂度
- 递归栈深度:最大为
2n
,因此空间复杂度为 O(n)。
7. 示例与测试
输入输出示例
- 输入:
n = 3
- 输出:
["((()))", "(()())", "(())()", "()(())", "()()()"]
测试代码
public static void main(String[] args) {
GenerateParentheses solution = new GenerateParentheses();
List<String> result = solution.generateParenthesis(3);
System.out.println(result); // 验证输出结果
}
8. 总结与扩展
回溯算法的优势
- 避免无效搜索:通过条件剪枝减少递归次数。
- 代码简洁:递归逻辑清晰,易于实现。
扩展思路
- 动态规划:可预先计算子问题的解并合并结果(如
dp[i] = "(" + dp[j] + ")" + dp[i-j-1]
)。 - 迭代法:利用栈或队列模拟递归过程。
通过本文,可以深入理解回溯算法在括号生成问题中的应用,并掌握其实现细节与优化方法。