0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

详解一道高频算法题:括号生成

算法与数据结构 来源:五分钟学算法 2020-06-03 17:19 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

题目描述

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

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

[ "((()))", "(()())", "(())()", "()(())", "()()()" ] 题目解析

方法一:回溯算法(深度优先遍历)

如果完成一件事情有很多种方法,并且每一种方法分成若干步骤,那多半就可以使用“回溯”算法完成。

“回溯”算法的基本思想是“尝试搜索”,一条路如果走不通(不能得到想要的结果),就回到上一个“路口”,尝试走另一条路。

因此,“回溯”算法的时间复杂度一般不低。如果能提前分析出,走这一条路并不能得到想要的结果,可以跳过这个分支,这一步操作叫“剪枝”。

做“回溯”算法问题的基本套路是:

1、使用题目中给出的示例,画树形结构图,以便分析出递归结构;

一般来说,树形图不用画完,就能够分析出递归结构和解题思路。

2、分析一个结点可以产生枝叶的条件、递归到哪里终止、是否可以剪枝、符合题意的结果在什么地方出现(可能在叶子结点,也可能在中间的结点);

3、完成以上两步以后,就要编写代码实现上述分析的过程,使用代码在画出的树形结构上搜索符合题意的结果。

在树形结构上搜索结果集,使用的方法是执行一次“深度优先遍历”。在遍历的过程中,可能需要使用“状态变量”。

(“广度优先遍历”当然也是可以的,请参考方法二。)

我们以 n = 2 为例,画树形结构图。

题解配图(1)

画图以后,可以分析出的结论:

左右都有可以使用的括号数量,即严格大于 0 的时候,才产生分支;

左边不受右边的限制,它只受自己的约束;

右边除了自己的限制以外,还收到左边的限制,即:右边剩余可以使用的括号数量一定得在严格大于左边剩余的数量的时候,才可以“节外生枝”;

在左边和右边剩余的括号数都等于 0 的时候结算。

参考代码如下:

publicclassSolution{ publicListgenerateParenthesis(intn){ Listres=newArrayList<>(); //特判 if(n==0){ returnres; } //执行深度优先遍历,搜索可能的结果 dfs("",n,n,res); returnres; } /** *@paramcurStr当前递归得到的结果 *@paramleft左括号还有几个没有用掉 *@paramright右边的括号还有几个没有用掉 *@paramres结果集 */ privatevoiddfs(StringcurStr,intleft,intright,Listres){ //因为是递归函数,所以先写递归终止条件 if(left==0&&right==0){ res.add(curStr); return; } //因为每一次尝试,都使用新的字符串变量,所以没有显式的回溯过程 //在递归终止的时候,直接把它添加到结果集即可,与「力扣」第46题、第39题区分 //如果左边还有剩余,继续递归下去 if(left>0){ //拼接上一个左括号,并且剩余的左括号个数减1 dfs(curStr+"(",left-1,right,res); } //什么时候可以用右边?例如,((((((),此时 left < right,         // 不能用等号,因为只有先拼了左括号,才能拼上右括号         if (right >0&&left< right) {             // 拼接上一个右括号,并且剩余的右括号个数减 1             dfs(curStr + ")", left, right - 1, res);         }     } }

如果我们不用减法,使用加法,即 left 表示“左括号还有几个没有用掉”,right 表示“右括号还有几个没有用掉”,可以画出另一棵递归树。

题解配图(2)

参考代码如下:

publicclassSolution{ //方法 2:用加法 publicListgenerateParenthesis(intn){ Listres=newArrayList<>(); //特判 if(n==0){ returnres; } //这里的dfs是隐式回溯 dfs("",0,0,n,res); returnres; } /** *@paramcurStr当前递归得到的结果 *@paramleft左括号用了几个 *@paramright右括号用了几个 *@paramn左括号、右括号一共用几个 *@paramres结果集 */ privatevoiddfs(StringcurStr,intleft,intright,intn,Listres){ //因为是递归函数,所以先写递归终止条件 if(left==n&&right==n){ res.add(curStr); return; } //如果左括号还没凑够,继续凑 if(left< n) {             // 拼接上一个左括号,并且剩余的左括号个数减 1             dfs(curStr + "(", left + 1, right, n, res);         }         // 什么时候可以用右边?例如,((((((),此时 left > right, //不能用等号,因为只有先拼了左括号,才能拼上右括号 if(right< n && left >right){ //拼接上一个右括号,并且剩余的右括号个数减1 dfs(curStr+")",left,right+1,n,res); } } }

方法二:广度优先遍历

还是上面的题解配图(1),使用广度优先遍历,结果集都在最后一层,即叶子结点处得到所有的结果集,编写代码如下。

publicclassSolution{ classNode{ /** *当前得到的字符串 */ privateStringres; /** *剩余左括号数量 */ privateintleft; /** *剩余右括号数量 */ privateintright; publicNode(Stringres,intleft,intright){ this.res=res; this.left=left; this.right=right; } @Override publicStringtoString(){ return"Node{"+ "res='"+res+'''+ ",left="+left+ ",right="+right+ '}'; } } publicListgenerateParenthesis(intn){ Listres=newArrayList<>(); if(n==0){ returnres; } Queuequeue=newLinkedList<>(); queue.offer(newNode("",n,n)); //总共需要拼凑的字符总数是2*n n=2*n; while(n>0){ intsize=queue.size(); for(inti=0;i< size; i++) {                 Node curNode = queue.poll();                 if (curNode.left >0){ queue.offer(newNode(curNode.res+"(",curNode.left-1,curNode.right)); } if(curNode.right>0&&curNode.left< curNode.right) {                     queue.offer(new Node(curNode.res + ")", curNode.left, curNode.right - 1));                 }             }             n--;         }         // 最后一层就是题目要求的结果集         while (!queue.isEmpty()) {             res.add(queue.poll().res);         }         return res;     } } 

方法三:动态规划

第 1 步:定义状态 dp[i]

使用 i 对括号能够生成的组合。

注意:每一个状态都是列表的形式。

第 2 步:状态转移方程:

i 对括号的一个组合,在 i - 1 对括号的基础上得到;

i 对括号的一个组合,一定以左括号 "(" 开始(不一定以 ")" 结尾),为此,我们可以枚举右括号 ")" 的位置,得到所有的组合;

枚举的方式就是枚举左括号 "(" 和右括号 ")" 中间可能的合法的括号对数,而剩下的合法的括号对数在与第一个左括号 "(" 配对的右括号 ")" 的后面,这就用到了以前的状态。

状态转移方程是:

dp[i] = "(" + dp[可能的括号对数] + ")" + dp[剩下的括号对数]

“可能的括号对数” 与 “剩下的括号对数” 之和得为 i,故“可能的括号对数” j 可以从 0 开始,最多不能超过 i, 即 i - 1;

“剩下的括号对数” + j = i - 1,故 “剩下的括号对数” = i - j - 1。

整理得:

dp[i] = "(" + dp[j] + ")" + dp[i- j - 1] , j = 0, 1, ..., i - 1

第 3 步:思考初始状态和输出:

初始状态:因为我们需要 0 对括号这种状态,因此状态数组 dp 从 0 开始,0 个括号当然就是 [""]。
输出:dp[n] 。
这个方法暂且就叫它动态规划,这么用也是很神奇的,它有下面两个特点:

1、自底向上:从小规模问题开始,逐渐得到大规模问题的解集;

2、无后效性:后面的结果的得到,不会影响到前面的结果。

publicclassSolution{ //把结果集保存在动态规划的数组里 publicListgenerateParenthesis(intn){ if(n==0){ returnnewArrayList<>(); } //这里dp数组我们把它变成列表的样子,方便调用而已 List>dp=newArrayList<>(n); Listdp0=newArrayList<>(); dp0.add(""); dp.add(dp0); for(inti=1;i<= n; i++) {             Listcur=newArrayList<>(); for(intj=0;j< i; j++) {                 Liststr1=dp.get(j); Liststr2=dp.get(i-1-j); for(Strings1:str1){ for(Strings2:str2){ //枚举右括号的位置 cur.add("("+s1+")"+s2); } } } dp.add(cur); } returndp.get(n); } }

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 算法
    +关注

    关注

    23

    文章

    4800

    浏览量

    98503
  • 递归
    +关注

    关注

    0

    文章

    29

    浏览量

    9302

原文标题:超详细!详解一道高频算法题:括号生成

文章出处:【微信号:TheAlgorithm,微信公众号:算法与数据结构】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    芯片的“第一道体检”:文读懂CP测试,半导体人必看!

    在芯片从晶圆到成品的漫长旅程里,有一道看不见却至关重要的关卡——CP测试。它被称为芯片良率的“守门员”、封装成本的“节流阀”,更是半导体产业链里前端制造与后端封测之间的关键枢纽。今天这篇,用通俗
    的头像 发表于 04-17 10:03 258次阅读
    芯片的“第<b class='flag-5'>一道</b>体检”:<b class='flag-5'>一</b>文读懂CP测试,半导体人必看!

    EOL测试系统——电池包下线前的最后一道品质防线

    在储能电池包的生产线上,EOL测试系统 是产品交付前的最后一道关口,也是最重要的品质防线。EOL测试的全面性与可靠性,直接决定了流入市场的储能产品是否存在早期失效风险。 个完整的电池包EOL测试
    的头像 发表于 03-23 16:40 341次阅读
    EOL测试系统——电池包下线前的最后<b class='flag-5'>一道</b>品质防线

    嵌入式春招笔试高频算法(附解题思路)

    的应届生、新手打造,整理了5嵌入式春招笔试高频算法,覆盖数组、链表、字符串、进制转换等核心考点,每道都附上“
    发表于 03-18 10:08

    红区破局之道:为什么防逆流装置是“第一道防线”?

    当之无愧的“第一道防线”。它不像四可装置那样兼具“可观可测、可控可调”的全链条能力,却能以最直接、最精准的方式,守住红区光伏并网的安全底线,为后续精细化管控、高效消纳筑牢基础——没有这道防线,红区光伏的合规运营、收益保障都将无从谈起。
    的头像 发表于 03-16 15:32 766次阅读
    红区破局之道:为什么防逆流装置是“第<b class='flag-5'>一道</b>防线”?

    智慧矿山能耗监测新模式!投入减半,运维0成本!

    国家“双碳”战略的发条越拧越紧。对于矿山、化工、建材等高耗能行业来说,这不再是一道“加分”,而是一道“生死”。尤其是矿石选别环节,破碎、筛分、磨矿三大工序的电耗占比往往超过全厂的7
    的头像 发表于 02-04 17:02 576次阅读
    智慧矿山能耗监测新模式!投入减半,运维0成本!

    高频芯片衰减器ATS系列:设计与应用详解

    高频芯片衰减器ATS系列:设计与应用详解 在电子工程领域,高频芯片衰减器是无线通信等系统中不可或缺的关键组件。今天,我们就来详细探讨下SSMSUSUMU的ATS系列
    的头像 发表于 02-03 17:20 1206次阅读

    高频芯片衰减器 ATS 系列:设计与应用详解

    高频芯片衰减器 ATS 系列:设计与应用详解高频电子设计领域,芯片衰减器是不可或缺的关键组件。今天,我们就来详细探讨下 ATS 系列高频
    的头像 发表于 02-03 15:30 273次阅读

    3秒响应、实时告警!智能井盖如何成为城市安全的“第一道防线”?

    IP68防护、-40℃~80℃宽温运行及10年超长续航,支持自定义报警阈值与多级告警机制,大幅降低误报率。作为城市物联网感知层的关键节点,智能井盖已融入智慧城管与应急管理体系,成为守护市民脚下安全的“第一道防线”。
    的头像 发表于 12-09 11:57 453次阅读
    3秒响应、实时告警!智能井盖如何成为城市安全的“第<b class='flag-5'>一道</b>防线”?

    openDACS 2025 开源EDA与芯片赛项 赛七:基于大模型的生成式原理图设计

    领域,对促进产业高质量发展具有重要意义。本赛项包含7,下面是赛七 基于大模型的生成式原理图设计的介绍。 2. 命题单位及赛Chai
    发表于 11-13 11:49

    不间断电源(UPS):电力保障的“最后一道防线”

    (UninterruptiblePowerSupply,简称UPS)作为电力保障的“最后一道防线”,通过储能装置与智能转换技术,在市电中断时实现零切换时间供电,成为现代社会的“电力守护者”。、UP
    的头像 发表于 10-29 09:02 1926次阅读
    不间断电源(UPS):电力保障的“最后<b class='flag-5'>一道</b>防线”

    电能质量在线监测装置的高频噪声滤波功能有哪些参数可以配置?

    )动态调整,以实现 “精准滤除噪声、完整保留有用信号” 的目标。以下是可配置的核心参数及其工程意义: 、硬件滤波参数(信号采集前端) 硬件滤波是高频噪声抑制的 “第一道防线”,其参数配置直接影响噪声衰减能力与有用信号完整性。
    的头像 发表于 10-15 16:43 731次阅读

    数据滤波算法的具体实现步骤是怎样的?

    高频电磁、瞬时脉冲等),选择适配的滤波算法并落地。以下以电能质量监测中最常用的 IIR 低通滤波(抗高频干扰)、滑动平均滤波(抗瞬时脉冲)、卡尔曼滤波(抗动态波动) 为例,详解具体实
    的头像 发表于 10-10 16:45 995次阅读

    非对称密钥生成和转换规格详解

    Signature Algorithm),是种基于模算数和整数有限域离散对数难题的种公钥密码算法,常用于数字签名和验签,不能用于加解密。 当前支持使用字符串参数和密钥参数两种方式生成
    发表于 09-01 07:50

    顶坚国产防爆手持终端如何成为石化企业安全生产的第一道防线

    顶坚国产防爆手持终端之所以能成为石化企业安全生产的第一道防线,源于其通过防爆设计、功能集成、实时交互与系统协同,从物理安全、功能安全、管理安全、应急安全等维度,覆盖了安全生产的全流程(预防、监测
    的头像 发表于 08-26 10:31 973次阅读
    顶坚国产防爆手持终端如何成为石化企业安全生产的第<b class='flag-5'>一道</b>防线

    信号发生器如何与波束赋形算法配合优化?

    场景中的多径传播、干扰和用户移动性,从而验证和优化波束赋形算法的性能。以下是具体配合优化方法及实施步骤:、信号发生器在波束赋形优化中的核心作用 模拟多径信道环境 功能:生成包含多径时延、角度扩展
    发表于 08-08 14:41