LeetCode 第22题:括号生成

LeetCode 第22题:括号生成

题目描述

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

难度

中等

题目链接

https://leetcode.cn/problems/generate-parentheses/

示例

示例 1:

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

示例 2:

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

提示

  • 1 <= n <= 8

解题思路

方法一:回溯法

这道题可以使用回溯法,通过跟踪左括号和右括号的数量来生成所有有效组合。

关键点:

  1. 左括号数量不能超过n
  2. 右括号数量不能超过左括号数量
  3. 当左右括号数量都等于n时,得到一个有效组合

具体步骤:

  1. 使用StringBuilder构建当前括号组合
  2. 记录已使用的左括号和右括号数量
  3. 递归尝试添加左括号或右括号
  4. 满足条件时将结果加入列表

时间复杂度:O(4ⁿ/√n),这是第n个卡特兰数的渐近复杂度
空间复杂度:O(n),递归栈的深度

方法二:动态规划

也可以使用动态规划来解决,通过构建子问题的解来得到最终结果。

思路:

  1. dp[i]表示i对括号的所有有效组合
  2. 对于dp[i],可以通过在dp[j]的基础上添加括号得到
  3. 利用已经计算出的子问题解

代码实现

C# 实现(回溯法)

public class Solution {
    public IList<string> GenerateParenthesis(int n) {
        List<string> result = new List<string>();
        BackTrack(result, new StringBuilder(), 0, 0, n);
        return result;
    }
  
    private void BackTrack(List<string> result, StringBuilder current, 
                          int leftCount, int rightCount, int n) {
        // 如果当前字符串长度等于2n,说明找到了一个有效组合
        if (current.Length == n * 2) {
            result.Add(current.ToString());
            return;
        }
      
        // 如果左括号数量小于n,可以添加左括号
        if (leftCount < n) {
            current.Append('(');
            BackTrack(result, current, leftCount + 1, rightCount, n);
            current.Length--; // 回溯,删除刚才添加的括号
        }
      
        // 如果右括号数量小于左括号数量,可以添加右括号
        if (rightCount < leftCount) {
            current.Append(')');
            BackTrack(result, current, leftCount, rightCount + 1, n);
            current.Length--; // 回溯,删除刚才添加的括号
        }
    }
}

C# 实现(动态规划)

public class Solution {
    public IList<string> GenerateParenthesis(int n) {
        List<List<string>> dp = new List<List<string>>();
        dp.Add(new List<string> { "" }); // dp[0]
      
        for (int i = 1; i <= n; i++) {
            List<string> current = new List<string>();
            for (int j = 0; j < i; j++) {
                foreach (string left in dp[j]) {
                    foreach (string right in dp[i - 1 - j]) {
                        current.Add($"({left}){right}");
                    }
                }
            }
            dp.Add(current);
        }
      
        return dp[n];
    }
}

代码详解

回溯版本:

  1. 使用StringBuilder:
    • 比字符串拼接更高效
    • 方便回溯时删除字符
  2. 回溯条件:
    • leftCount < n 时可以添加左括号
    • rightCount < leftCount 时可以添加右括号
  3. 终止条件:
    • 当前字符串长度等于2n

动态规划版本:

  1. 状态定义:
    • dp[i]表示i对括号的所有有效组合
  2. 状态转移:
    • 通过在子问题解的基础上添加括号
  3. 构建方式:
    • 使用两层循环遍历所有可能的组合

执行结果

回溯版本:

  • 执行用时:96 ms
  • 内存消耗:45.2 MB

动态规划版本:

  • 执行用时:88 ms
  • 内存消耗:46.8 MB

总结与反思

  1. 这是一道经典的回溯算法题目:
    • 考察对回溯思想的理解
    • 考察括号匹配规则的应用
    • 考察代码的优化能力
  2. 两种解法比较:
    • 回溯:思路直观,实现简单
    • 动态规划:效率更高,但实现较复杂
  3. 优化思路:
    • 使用StringBuilder提高效率
    • 剪枝优化回溯过程
    • 合理使用递归和迭代

相关题目

posted @   旧厂街小江  阅读(2)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示