当前位置: 首页 > news >正文

【LeetCode 热题 100】22. 括号生成——(解法一)选左括号还是选有括号

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

文章目录

  • 整体思路
  • 完整代码
  • 时空复杂度
    • 时间复杂度:O(C_n) 或 O(4^n / n^(3/2))
    • 空间复杂度:O(n)

整体思路

这段代码旨在解决一个经典的组合生成问题:括号生成 (Generate Parentheses)。问题要求给定一个整数 n,生成所有由 n 对括号组成的、格式正确的括号组合。

该算法采用了 深度优先搜索 (DFS) 结合 剪枝 (Pruning) 的策略来构建所有合法的括号字符串。它通过维护左右括号的数量来确保在构建过程中的每一步都满足括号合法性的基本规则。

  1. 算法核心:递归与状态

    • 算法的主体是一个 dfs 递归函数。这个函数负责在字符数组 path 中逐步构建括号字符串。
    • 核心状态dfs 函数通过三个关键参数来追踪构建过程:
      • left: 已使用的左括号 ( 的数量。
      • right: 已使用的右括号 ) 的数量。
      • n: 需要生成的括号对数,是一个固定目标。
    • path 数组用于存储当前正在构建的字符串,其总长度为 2nleft + right 恰好是当前要填充的 path 数组的索引。
  2. 递归的终止条件

    • if (right == n):当已使用的右括号数量达到 n 时,意味着已使用的左括号数量也必然达到了 n(因为right不可能超过left),此时一个长度为 2n 的完整且合法的括号字符串已经构建完成。
    • path 字符数组转换为字符串 new String(path),并将其加入最终结果 ans 列表中,然后返回。
  3. 探索与剪枝(核心选择逻辑)

    • dfs 的每一步,我们都有两种可能的操作:添加一个左括号或添加一个右括号。但这些操作必须在满足特定条件(即剪枝规则)时才能进行,以保证字符串的合法性。
      a. 添加左括号 (

      • 条件if (left < n)。只要已使用的左括号数量还没有达到 n,我们就可以安全地添加一个新的左括号。
      • 操作:将 ( 放入 path 的当前位置,然后进行递归调用 dfs(left + 1, right, ...),更新状态。

      b. 添加右括号 )

      • 条件if (right < left)。这是一个至关重要的剪枝规则。为了保证括号的合法性,任何时候,已使用的右括号数量都不能超过左括号的数量。只有当 right < left 时,添加一个右括号才不会破坏这个规则。
      • 操作:将 ) 放入 path 的当前位置,然后进行递归调用 dfs(left, right + 1, ...),更新状态。
  4. 隐式回溯

    • 这个实现中没有显式的“撤销选择”操作(如 path.removeLast())。这是因为它使用的是一个固定大小的字符数组 path。当一个递归分支返回后,上一个调用栈帧会继续执行,它可能会在 path 的同一个位置上覆盖新的字符(例如,先尝试放 (,返回后再尝试放 ))。这种通过覆盖实现状态恢复的方式,是一种隐式的回溯。

完整代码

class Solution {/*** 生成所有 n 对有效括号的组合。* @param n 括号的对数* @return 所有有效括号组合的列表*/public List<String> generateParenthesis(int n) {// ans: 存储所有符合条件的最终组合List<String> ans = new ArrayList<>();// path: 一个字符数组,用于构建括号字符串,总长度为 2nchar[] path = new char[n * 2];// 开始深度优先搜索,初始时左右括号都为0dfs(0, 0, n, ans, path);return ans;}/*** 深度优先搜索(DFS)辅助函数* @param left  已使用的左括号 '(' 的数量* @param right 已使用的右括号 ')' 的数量* @param n     目标括号对数* @param ans   结果列表* @param path  当前正在构建的字符数组路径*/private void dfs(int left, int right, int n, List<String> ans, char[] path) {// 递归终止条件:当已使用的右括号数量达到 n 时,// 说明一个完整的、长度为 2n 的合法括号串已经构建完成。if (right == n) {// 将字符数组路径转换为字符串,并加入结果集ans.add(new String(path));return;}// --- 核心选择与剪枝逻辑 ---// 选择 1: 放置一个左括号 '('// 条件(剪枝):只要已使用的左括号数量还未达到 n,就可以放置。if (left < n) {// 将 '(' 放置在当前路径的末尾 (索引为 left + right)path[left + right] = '(';// 递归进入下一层,更新 left 的数量dfs(left + 1, right, n, ans, path);}// 选择 2: 放置一个右括号 ')'// 条件(剪枝):为了保证括号合法性,任何时候右括号的数量都不能超过左括号。if (right < left) {// 将 ')' 放置在当前路径的末尾path[left + right] = ')';// 递归进入下一层,更新 right 的数量dfs(left, right + 1, n, ans, path);}}
}

时空复杂度

时间复杂度:O(C_n) 或 O(4^n / n^(3/2))

  1. 这个问题的解的数量是卡特兰数 (Catalan number),第 n 个卡特兰数 C_n 约等于 4^n / (n * sqrt(πn))
  2. DFS 算法会访问一个搜索树,该树的叶子节点对应着每个有效的括号组合。
  3. 在每个叶子节点,我们需要 O(n) 的时间来创建一个新的字符串 new String(path)
  4. 搜索树的节点总数与卡特兰数成正比。总的来说,算法的时间复杂度与解的数量和每个解的长度有关。
  5. 因此,时间复杂度可以表示为 O(n * C_n),或者更紧凑地写为 O(4^n / n^(3/2))。这是一个指数级复杂度。

空间复杂度:O(n)

  1. 递归栈深度:递归的最大深度发生在构建一条形如 ((...)) 的路径时,此时会连续调用 n 次来放置左括号,然后再调用 n 次来放置右括号。因此,递归栈的最大深度是 2n。所以,这部分空间复杂度是 O(n)
  2. path 数组path 字符数组的长度是 2n,占用了 O(n) 的空间。
  3. 结果列表 ans:存储最终结果的空间不计入算法的额外辅助空间复杂度。

综合分析
算法所需的额外空间由递归栈和 path 数组决定,两者都是 O(n) 级别。因此,空间复杂度为 O(n)

参考灵神

http://www.xdnf.cn/news/1181791.html

相关文章:

  • 基于粒子群优化的PID控制在药液流量控制系统中的应用
  • Python常用医疗AI库以及案例解析(场景化进阶版)
  • SqlRest让SQL秒变Http API,还支持20+数据库(含国产数据库)
  • 100条常用SQL语句大全
  • C++20协程异步
  • 论文Review Registration TEASER | TRO | MIT出品,点云配准经典BenchMark | 硬核的数学长文
  • 蜘蛛强引的原理与百度SEO的关系
  • Qt(资源库和按钮组)
  • SpringBoot+AI+Web3实战指南
  • pytest官方Tutorial所有示例详解(一)
  • 洛谷刷题7.24
  • 优选算法:移动零
  • 计算机网络知识点总结 (2)
  • go语言基础教程:1. Go 下载安装和设置
  • Java网络编程入门:从基础原理到实践(二)
  • 算法第三十八天:动态规划part06(第九章)
  • uboot FPGA调试环境搭建
  • io_uring:Linux异步I/O的革命性突破
  • 星慈光编程虫2号小车讲解第四篇--触摸按键
  • 平时遇到的错误码及场景?404?400?502?都是什么场景下什么含义,该怎么做 ?
  • vue3核心语法
  • (进阶向)Python第十四期OpenCv图像预处理方法[2]
  • 跨境支付入门~国际支付结算(稳定币)
  • 深度分析Java多线程机制
  • AI实践:Pydantic
  • Spring之SSM整合流程详解(Spring+SpringMVC+MyBatis)
  • 【Linux】常用命令(一)
  • 洛谷P1512 伊甸园日历游戏
  • SQL基础⑫ | 视图篇
  • C++ - 仿 RabbitMQ 实现消息队列--服务端核心模块实现(三)