动态规划 括号生成

22. 括号生成

问题描述

给定一个整数 n,生成所有由 n 对括号组成的有效组合。有效组合需满足:

  1. 左括号和右括号数量相等
  2. 任意位置左括号数量不少于右括号

示例

示例 1:

输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]

示例 2:

输入:n = 1
输出:["()"]

算法思路

方法一:BFS(广度优先搜索)

  1. 使用队列存储中间状态(当前字符串、左括号数、右括号数)
  2. 逐层扩展,每次添加一个括号
  3. 当字符串长度达到 2n 时,将有效组合加入结果集

方法二:回溯法(DFS)

  1. 核心思想:递归构建字符串,通过剪枝条件保证有效性
  2. 状态变量
    • cur:当前构建的字符串
    • left:已用左括号数
    • right:已用右括号数
  3. 递归规则
    • 左括号数量小于 n → 添加左括号
    • 右括号数量小于左括号 → 添加右括号
  4. 终止条件:字符串长度等于 2*n

方法三:动态规划

  1. 定义状态dp[i] 表示由 i 对括号组成的所有有效组合
  2. 状态转移
    dp[i] = "(" + dp[j] + ")" + dp[i-j-1]
    0 ≤ j < i,内层括号数 j + 外层括号数 i-j-1 = i-1
  3. 初始状态dp[0] = [""]

代码实现

方法一:BFS(广度优先搜索)

class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> result = new ArrayList<>();
        if (n == 0) return result;
        
        // 使用队列存储节点:当前字符串 + 括号计数
        Queue<Pair<String, int[]>> queue = new LinkedList<>();
        queue.offer(new Pair<>("", new int[]{0, 0})); // 初始状态
        
        while (!queue.isEmpty()) {
            Pair<String, int[]> pair = queue.poll();
            // 当前字符串
            String cur = pair.getKey();
            // 左括号
            int left = pair.getValue()[0];
            // 右括号
            int right = pair.getValue()[1];
            
            // 终止条件:达到完整长度
            if (cur.length() == 2 * n) {
                result.add(cur);
                continue;
            }
            
            // 添加左括号(如果允许)
            if (left < n) {
                queue.offer(new Pair<>(cur + "(", new int[]{left + 1, right}));
            }
            
            // 添加右括号(如果允许)
            if (right < left) {
                queue.offer(new Pair<>(cur + ")", new int[]{left, right + 1}));
            }
        }
        return result;
    }
    
    // 辅助类:存储字符串和括号计数
    static class Pair<K, V> {
        K key;
        V value;
        Pair(K key, V value) {
            this.key = key;
            this.value = value;
        }
        K getKey() { return key; }
        V getValue() { return value; }
    }
}

方法二:回溯法(DFS)

class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> result = new ArrayList<>();
        backtrack(result, "", 0, 0, n);
        return result;
    }
    
    /**
     * 回溯递归函数
     * 
     * @param result 结果列表
     * @param cur    当前构建的字符串
     * @param left   已使用的左括号数
     * @param right  已使用的右括号数
     * @param n      括号对数
     */
    private void backtrack(List<String> result, String cur, int left, int right, int n) {
        // 终止条件:字符串长度达到2n
        if (cur.length() == 2 * n) {
            result.add(cur);
            return;
        }
        
        // 左括号数量小于n → 添加左括号
        if (left < n) {
            backtrack(result, cur + "(", left + 1, right, n);
        }
        
        // 右括号数量小于左括号 → 添加右括号
        if (right < left) {
            backtrack(result, cur + ")", left, right + 1, n);
        }
    }
}

方法三:动态规划

class Solution {
    public List<String> generateParenthesis(int n) {
        // dp[i] 存储i对括号的所有有效组合
        List<List<String>> dp = new ArrayList<>();
        
        // 初始化:0对括号
        List<String> dp0 = new ArrayList<>();
        dp0.add("");
        dp.add(dp0);
        
        // 计算dp[1]到dp[n]
        for (int i = 1; i <= n; i++) {
            List<String> curList = new ArrayList<>();
            
            // 状态转移:枚举内层括号数量j
            for (int j = 0; j < i; j++) {
                // 内层组合:dp[j]
                for (String inner : dp.get(j)) {
                    // 外层组合:dp[i-j-1]
                    for (String outer : dp.get(i - j - 1)) {
                        // 组合形式:"(" + inner + ")" + outer
                        curList.add("(" + inner + ")" + outer);
                    }
                }
            }
            dp.add(curList);
        }
        return dp.get(n);
    }
}

算法分析

方法时间复杂度空间复杂度特点
回溯(DFS)O(4^n/√n)O(n)直观,易理解
BFSO(4^n/√n)O(4^n/√n)按层生成,避免递归栈溢出
动态规划O(4^n/√n)O(4^n/√n)自底向上,利用子问题解

算法过程(n=2)

回溯法(DFS)

递归路径当前字符串leftright操作
初始“”00
分支1“(”10添加左括号
分支1.1“((”20添加左括号
分支1.1.1“(()”21添加右括号
分支1.1.2“(())”22添加右括号 → 加入结果
分支1.2“()”11添加右括号
分支1.2.1“()(”21添加左括号
分支1.2.2“()()”22添加右括号 → 加入结果

动态规划

  1. 初始状态
    dp[0] = [""]

  2. 计算 dp[1]
    j=0: "(" + dp[0] + ")" + dp[0] = "()"
    dp[1] = ["()"]

  3. 计算 dp[2]

    • j=0: "(" + dp[0] + ")" + dp[1] = "()()"
    • j=1: "(" + dp[1] + ")" + dp[0] = "(())"
      dp[2] = ["()()", "(())"]

测试用例

public static void main(String[] args) {
    Solution sol = new Solution();
    
    // 测试用例:n=3
    List<String> result = sol.generateParenthesis(3);
    // 预期输出:["((()))","(()())","(())()","()(())","()()()"]
    System.out.println(result);
    
    // 边界测试:n=0
    System.out.println(sol.generateParenthesis(0)); // [""]
    
    // 边界测试:n=1
    System.out.println(sol.generateParenthesis(1)); // ["()"]
}

关键点

  1. 回溯法(DFS)核心

    • 剪枝条件
      • 左括号不超过 n
      • 右括号不超过左括号
    • 递归终止:当字符串长度达到 2n 时保存结果
    • 无效组合避免:通过 right < left 保证任意位置左括号不少于右括号
  2. BFS核心

    • 使用队列按层扩展状态
    • 节点存储当前字符串和括号计数
    • 自动实现广度优先遍历
  3. 动态规划核心

    • 状态定义:dp[i]i 对括号的有效组合
    • 状态转移:"(" + dp[j] + ")" + dp[i-j-1]
    • 自底向上计算,避免重复计算
  4. 共同约束

    • 任意位置左括号 ≥ 右括号
    • 最终左右括号数量相等

常见问题

  1. 为什么不需要检查组合有效性?
    剪枝条件 right < left 保证了任意位置左括号数量 ≥ 右括号数量,最终组合必然有效。

  2. 如何处理重复组合?
    递归天然避免重复,每个位置的选择独立且符合语法规则。

  3. 为什么时间复杂度是 O(4^n/√n)?
    解的数量是卡特兰数 C_n = (1/(n+1)) * (2n choose n) ≈ 4^n/(n√(πn))。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值