【LeetCode】22. 括号生成
文章目录
- 22. 括号生成
- 📝 题目描述
- 💡 解题思路(仿照 96:结构 + 计数)
- 回溯核心思想(合法性剪枝)
- 生成式动态规划(结构拼接)
- 数量验证(卡塔兰数)
- 📊 复杂度分析
- 🎯 示例(n=3)
- 💻 运行方式
- 🧪 预期输出(节选)
- 🔎 相关题目
- 完整题解代码
22. 括号生成
📝 题目描述
数字 n 代表生成括号的对数,请你设计一个函数,用于生成所有可能且有效的括号组合。
- 输入:n(1 <= n <= 8)
- 输出:所有有效括号字符串的数组,如 n=3 → [“((()))”,“(()())”,“(())()”,“()(())”,“()()()”]
💡 解题思路(仿照 96:结构 + 计数)
这是一道典型的卡塔兰结构题。常见三种角度:
- 回溯(合法性剪枝)— 推荐,用状态约束生成所有解。
- 生成式动态规划(按结构拼接)— 利用“(A)B”的结构划分构造所有解。
- 卡塔兰数计数— 只用于验证数量是否正确,不生成解。
回溯核心思想(合法性剪枝)
- 任何时刻放置 ‘)’ 不能超过 ‘(’ 的数量;
- ‘(’ 的数量不超过 n,‘)’ 的数量不超过 n;
- 当 leftrightn 时得到一个有效解。
flowchart LRS([start]) --> A{left < n?}A -- 是 --> P[加 "("] --> A2{right < left?}A2 -- 是 --> Q[加 ")"] --> A3{完成?}A2 -- 否 --> A3A3 -- 否 --> AA -- 否 --> B{right < left?}B -- 是 --> R[加 ")"] --> B2{完成?}B -- 否 --> E([剪枝])B2 -- 否 --> AA3 -- 是 --> T([记录答案])B2 -- 是 --> T
生成式动态规划(结构拼接)
- 定义:dp[i] 为生成 i 对括号的所有合法字符串;
- 递推:dp[i] = sum_{left=0…i-1} “(” + dp[left] + “)” + dp[i-1-left];
- 思想与「96. 不同的二叉搜索树」的卡塔兰结构划分一致。
graph TDA[dp[0] = [""]] --> B[计算 dp[1..n]]B --> C{i from 1..n}C --> D[left from 0..i-1]D --> E[for s1 in dp[left], s2 in dp[i-1-left]]E --> F[dp[i] += "(" + s1 + ")" + s2]
数量验证(卡塔兰数)
- C(n) = C(2n, n) / (n+1);
- n: 0 1 2 3 4 5 6 7 8
- C: 1 1 2 5 14 42 132 429 1430
📊 复杂度分析
算法 | 时间复杂度 | 空间复杂度 | 说明 |
---|---|---|---|
回溯(剪枝) | O(Cn) 生成量级(Cn 为卡塔兰数) | O(n) | 单条路径深度为 n |
生成式 DP(拼接) | O(Cn) 拼接生成 | O(Cn) | 存储所有子问题解集合 |
卡塔兰计数(仅计数) | O(n) | O(1) | 验证数量,不产出具体方案 |
🎯 示例(n=3)
输出(任意顺序均可):
((()))
(()())
(())()
()(())
()()()
回溯放置过程(路径示意):
( -> (( -> ((( -> ((() X-> (()( -> (()() -> (()()) ✓-> (() -> (()( -> (()() ✓
-> () -> ()( -> ()(( -> ()(() -> ()(()) ✓-> ()() -> ()()() ✓
💻 运行方式
cd 22
go run main.go
🧪 预期输出(节选)
=== 括号生成 算法测试 ===
n=1: 回溯=1, DP=1, Catalan=1
n=2: 回溯=2, DP=2, Catalan=2
n=3: 回溯=5, DP=5, Catalan=5
n=4: 回溯=14, DP=14, Catalan=14示例(n=3):
((()))
(()())
(())()
()(())
()()()
🔎 相关题目
-
- 有效的括号(判断合法性)
-
- 最长有效括号(字符串扫描)
-
- 不同的二叉搜索树(同属卡塔兰结构)
-
- 删除无效的括号(BFS/回溯)
回溯与卡塔兰结构相辅相成,构造与计数相互印证 ✅
完整题解代码
package mainimport ("fmt""sort"
)// 解法1:回溯(剪枝)
func generateParenthesisBacktrack(n int) []string {var ans []stringpath := make([]byte, 0, 2*n)var dfs func(l, r int)dfs = func(l, r int) {if l > n || r > l { // 剪枝return}if l == n && r == n {ans = append(ans, string(path))return}if l < n {path = append(path, '(')dfs(l+1, r)path = path[:len(path)-1]}if r < l {path = append(path, ')')dfs(l, r+1)path = path[:len(path)-1]}}dfs(0, 0)return ans
}// 解法2:生成式动态规划(卡塔兰结构拼接)
func generateParenthesisDP(n int) []string {dp := make([][]string, n+1)dp[0] = []string{""}for i := 1; i <= n; i++ {cur := make([]string, 0)for left := 0; left <= i-1; left++ {right := i - 1 - leftfor _, a := range dp[left] {for _, b := range dp[right] {cur = append(cur, "("+a+")"+b)}}}sort.Strings(cur) // 便于稳定输出dp[i] = cur}return dp[n]
}// 辅助:卡塔兰数计数(用于校验数量)
func catalan(n int) int {if n <= 1 {return 1}// C(n) = C(2n, n) / (n+1)num := 1den := 1for i := 1; i <= n; i++ {num *= (n + i) // 从 n+1 乘到 2nden *= i // 从 1 乘到 n}return num / den / (n + 1)
}func main() {fmt.Println("=== 括号生成 算法测试 ===")for _, n := range []int{1, 2, 3, 4} {a := generateParenthesisBacktrack(n)b := generateParenthesisDP(n)fmt.Printf("n=%d: 回溯=%d, DP=%d, Catalan=%d\n", n, len(a), len(b), catalan(n))}fmt.Println("\n示例(n=3):")for _, s := range generateParenthesisBacktrack(3) {fmt.Println(s)}
}