【Leetcode hot 100】22.括号生成
问题链接
22.括号生成
问题描述
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例 1:
输入:n = 3
输出:[“((()))”,“(()())”,“(())()”,“()(())”,“()()()”]
示例 2:
输入:n = 1
输出:[“()”]
提示:
1 <= n <= 8
问题解答
一、回溯法(推荐)
核心思路
回溯法通过「尝试生成括号 + 剪枝无效路径」实现:
- 状态跟踪:记录当前括号字符串、已使用的左括号数量、已使用的右括号数量。
- 终止条件:当字符串长度等于
2*n
(n 对括号共 2n 个字符)时,将其加入结果列表。 - 递归逻辑:
- 优先添加左括号:若左括号数量 < n,可继续加左括号(保证后续有机会补右括号)。
- 谨慎添加右括号:若右括号数量 < 左括号数量(避免出现
())
等无效组合),可加右括号。
- 剪枝:通过上述条件直接排除无效路径,无需生成所有组合后再筛选,效率更高。
代码实现
import java.util.ArrayList;
import java.util.List;class Solution {// 存储最终结果的列表private List<String> result = new ArrayList<>();public List<String> generateParenthesis(int n) {// 初始调用:空字符串、左括号0个、右括号0个backtrack("", 0, 0, n);return result;}/*** 回溯递归函数* @param current 当前构建的括号字符串* @param left 已使用的左括号数量* @param right 已使用的右括号数量* @param n 目标括号对数*/private void backtrack(String current, int left, int right, int n) {// 终止条件:字符串长度达到 2n(括号生成完成)if (current.length() == 2 * n) {result.add(current);return;}// 1. 尝试添加左括号:左括号未用完时可加if (left < n) {backtrack(current + "(", left + 1, right, n);}// 2. 尝试添加右括号:右括号数量 < 左括号数量时可加(避免无效组合)if (right < left) {backtrack(current + ")", left, right + 1, n);}}
}
复杂度分析
- 时间复杂度:O(4ⁿ / √n),生成的有效组合数为「卡特兰数」Cₙ,其渐近复杂度为 4ⁿ / √n。
- 空间复杂度:O(n),递归栈深度最大为 2n(字符串长度),且无额外存储(除结果列表外)。
二、动态规划法
核心思路
动态规划通过「子问题的解构建当前问题的解」,核心是找到状态转移规律:
- 状态定义:
dp[i]
表示生成i
对括号的所有有效组合列表。 - 基础 case:
dp[0] = [""]
(0 对括号只有空字符串一种情况)。 - 状态转移:对于
i
对括号,可拆分为「左半部分j
对 + 中间 1 对 + 右半部分i-j-1
对」,即:dp[i] = "(" + dp[j] + ")" + dp[i-j-1]
(其中j ∈ [0, i-1]
)- 例如
i=2
时,j=0
得()()
,j=1
得(())
,故dp[2] = ["()()", "(())"]
。
代码实现
import java.util.ArrayList;
import java.util.List;class Solution {public List<String> generateParenthesis(int n) {// dp[i] 存储 i 对括号的所有有效组合List<List<String>> dp = new ArrayList<>();// 基础 case:0 对括号只有空字符串dp.add(new ArrayList<>());dp.get(0).add("");// 从 1 对括号递推到 n 对括号for (int i = 1; i <= n; i++) {dp.add(new ArrayList<>()); // 初始化 dp[i]// j 表示左半部分的括号对数(0 ≤ j < i)for (int j = 0; j < i; j++) {// 遍历左半部分的所有组合(dp[j])for (String left : dp.get(j)) {// 遍历右半部分的所有组合(dp[i-j-1])for (String right : dp.get(i - j - 1)) {// 拼接:(左半)右半dp.get(i).add("(" + left + ")" + right);}}}}return dp.get(n); // 返回 n 对括号的组合}
}
复杂度分析
- 时间复杂度:O(4ⁿ / √n),与回溯法一致,需生成所有卡特兰数对应的组合。
- 空间复杂度:O(n * 4ⁿ / √n),需存储
dp[0]
到dp[n]
的所有组合,空间开销高于回溯法。
两种方法对比
方法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
回溯法 | 直观、空间开销小(无冗余存储) | 依赖递归,栈深度有限制 | 面试优先(思路易讲解) |
动态规划法 | 非递归、可复用子问题结果 | 空间开销大、思路较抽象 | 理解动态规划思想 |