LeetCode 0022 -- 括号生成

本文深入探讨了括号生成问题,通过回溯算法实现有效括号组合的生成,并提出优化策略,确保序列的有效性,避免无效组合。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

括号生成

题目描述

给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。

例如,给出 n = 3,生成结果为:

[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]

解题思路

个人AC

回溯算法可以用来穷尽复杂、递归问题的所有解。一般地,当遇到需要求出所有可能解的问题时,可以考虑从回溯(DFS)思想入手。

可以很容易地得出,递归深度depth跟括号对数n有关:depth = 2 * n

如果不考虑**“有效性”**而只考虑左右括号数量对等的话,一共有 C 2 n n C_{2n}^{n} C2nn种括号组合:

  • ()的数量超过n+1时,需要直接跳过。
class Solution {
    
    public List<String> generateParenthesis(int n) {
        HashMap<Character, Integer> braces = new HashMap<Character, Integer>() {{
            put('(', n);
            put(')', n);
        }};
        
        List<String> output = new ArrayList<>();
        backtrace(output, braces, "", 2 * n, 1);
        return output;
    }
    
    /**
      * @param restBraces:HashMap<Character, Integer>    剩余的括号数量
      * @param possible:String                           表示一种可能的解
      * @param n:int                                     最大递归深度
      * @param depth:int                                 递归深度
      */
    private void backtrace(List<String> output, HashMap<Character, Integer> restBraces, String possible, int n, int depth) {
        if (depth > n) {
            output.add(possible);
            return;
        }
        
        for (char brace : restBraces.keySet()) {
            if (restBraces.get(brace) <= 0) continue;
            restBraces.put(brace, restBraces.get(brace) - 1);
            backtrace(output, restBraces, possible + brace, n, depth + 1);
            
            // backtrace
            restBraces.put(brace, restBraces.get(brace) + 1);
        }
    }
}

但若要考虑**“有效性”的话,则需要从所有可能中剔除无效组合**(剪枝)。那么什么样的情况是无效的呢?

  • 如果当前附加的字符为(的话:
    • 直接DFS就可以了~~
  • 如果当前附加的字符为)的话:
    • 其左边(的数量等于)的数量,即加上当前的)会产生失衡,如()),直接跳过;
class Solution {
    
    public List<String> generateParenthesis(int n) {
        HashMap<Character, Integer> braces = new HashMap<Character, Integer>() {{
            put('(', n);
            put(')', n);
        }};
        
        List<String> output = new ArrayList<>();
        backtrace(output, braces, "", 2 * n, 1);
        return output;
    }
    
    /**
      * @param restBraces:HashMap<Character, Integer>    剩余的括号数量
      * @param possible:String                           表示一种可能的解
      * @param n:int                                     最大递归深度
      * @param depth:int                                 递归深度
      */
    private void backtrace(List<String> output, HashMap<Character, Integer> restBraces, String possible, int n, int depth) {
        if (depth > n) {
            output.add(possible);
            return;
        }
        
        for (char brace : restBraces.keySet()) {
            if (restBraces.get(brace) <= 0) continue;
            if (brace == ')' && (restBraces.get('(') == restBraces.get(')'))) {
                continue;
            }
            restBraces.put(brace, restBraces.get(brace) - 1);
            backtrace(output, restBraces, possible + brace, n, depth + 1);
            
            // backtrace
            restBraces.put(brace, restBraces.get(brace) + 1);
        }
    }
}

难受啊,为什么只击败了10 ~ 20%的提交啊~~还有什么地方可以优化的么?
在这里插入图片描述

最优解

只有在我们知道序列仍然保持有效时才添加 '(' or ')'。我们可以通过跟踪到目前为止放置的左括号和右括号的数目来做到这一点。

  • 如果我们(没有放完,我们可以放一个(
  • 如果)的数量不超过(的数量,我们可以放一个)
class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> output = new ArrayList();
        backtrack(output, "", 0, 0, n);
        return output;
    }

    public void backtrack(List<String> output, String possible, int open, int close, int max){
        if (possible.length() == max * 2) {
            output.add(possible);
            return;
        }

        if (open < max)
            backtrack(output, possible + "(", open + 1, close, max);
        if (close < open)
            backtrack(output, possible + ")", open, close + 1, max);
    }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值